From d7a25808333281838bd4d1fde63add8adaee1a25 Mon Sep 17 00:00:00 2001 From: Documentation Bot Date: Mon, 20 Jan 2025 06:28:06 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20QCoDeS/b?= =?UTF-8?q?roadbean@b377c6edf9c4963c93ab4aa7becc1787f3ec2360=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .buildinfo | 4 + .nojekyll | 0 .../examples_Pulse_Building_Tutorial_11_0.png | Bin 0 -> 7906 bytes .../examples_Pulse_Building_Tutorial_11_1.png | Bin 0 -> 14367 bytes .../examples_Pulse_Building_Tutorial_11_2.png | Bin 0 -> 12554 bytes .../examples_Pulse_Building_Tutorial_13_0.png | Bin 0 -> 9584 bytes .../examples_Pulse_Building_Tutorial_13_1.png | Bin 0 -> 9611 bytes .../examples_Pulse_Building_Tutorial_15_0.png | Bin 0 -> 18177 bytes .../examples_Pulse_Building_Tutorial_17_0.png | Bin 0 -> 19146 bytes .../examples_Pulse_Building_Tutorial_21_0.png | Bin 0 -> 23661 bytes .../examples_Pulse_Building_Tutorial_23_0.png | Bin 0 -> 25176 bytes .../examples_Pulse_Building_Tutorial_28_0.png | Bin 0 -> 25320 bytes .../examples_Pulse_Building_Tutorial_30_0.png | Bin 0 -> 8030 bytes .../examples_Pulse_Building_Tutorial_30_1.png | Bin 0 -> 9434 bytes .../examples_Pulse_Building_Tutorial_31_0.png | Bin 0 -> 9723 bytes .../examples_Pulse_Building_Tutorial_32_1.png | Bin 0 -> 17891 bytes .../examples_Pulse_Building_Tutorial_32_2.png | Bin 0 -> 24578 bytes .../examples_Pulse_Building_Tutorial_4_0.png | Bin 0 -> 18278 bytes .../examples_Pulse_Building_Tutorial_5_0.png | Bin 0 -> 21564 bytes .../examples_Pulse_Building_Tutorial_5_1.png | Bin 0 -> 20745 bytes .../examples_Pulse_Building_Tutorial_8_0.png | Bin 0 -> 16418 bytes .../examples_Pulse_Building_Tutorial_9_0.png | Bin 0 -> 16850 bytes .../examples_Pulse_Building_Tutorial_9_1.png | Bin 0 -> 15458 bytes _modules/broadbean/blueprint.html | 1259 +++ _modules/broadbean/broadbean.html | 530 ++ _modules/broadbean/element.html | 848 ++ _modules/broadbean/plotting.html | 659 ++ _modules/broadbean/ripasso.html | 509 ++ _modules/broadbean/sequence.html | 1677 ++++ _modules/broadbean/tools.html | 481 + _modules/index.html | 304 + _sources/api/generated/broadbean.rst.txt | 69 + _sources/changes/0.10.0.rst.txt | 18 + _sources/changes/0.11.0.rst.txt | 38 + _sources/changes/index.rst.txt | 6 + .../Example_Write_Read_JSON.ipynb.txt | 7096 +++++++++++++++ .../examples/Filter_compensation.ipynb.txt | 3483 ++++++++ ...g_output_for_Tektronix_AWG70000A.ipynb.txt | 1075 +++ .../Pulse_Building_Tutorial.ipynb.txt | 1287 +++ _sources/examples/Subsequences.ipynb.txt | 3459 ++++++++ _sources/examples/index.rst.txt | 12 + _sources/index.rst.txt | 39 + _sources/start/index.rst.txt | 141 + _static/basic.css | 914 ++ _static/debug.css | 69 + _static/doctools.js | 149 + _static/documentation_options.js | 13 + _static/file.png | Bin 0 -> 286 bytes _static/language_data.js | 192 + _static/minus.png | Bin 0 -> 90 bytes _static/nbsphinx-broken-thumbnail.svg | 9 + _static/nbsphinx-code-cells.css | 259 + _static/nbsphinx-gallery.css | 31 + _static/nbsphinx-no-thumbnail.svg | 9 + _static/plus.png | Bin 0 -> 90 bytes _static/pygments.css | 249 + _static/readme | 1 + _static/scripts/furo-extensions.js | 0 _static/scripts/furo.js | 3 + _static/scripts/furo.js.LICENSE.txt | 7 + _static/scripts/furo.js.map | 1 + _static/searchtools.js | 632 ++ _static/skeleton.css | 296 + _static/sphinx_highlight.js | 154 + _static/styles/furo-extensions.css | 2 + _static/styles/furo-extensions.css.map | 1 + _static/styles/furo.css | 2 + _static/styles/furo.css.map | 1 + api/generated/broadbean.html | 1692 ++++ changes/0.10.0.html | 349 + changes/0.11.0.html | 383 + changes/index.html | 340 + examples/Example_Write_Read_JSON.html | 7727 +++++++++++++++++ examples/Example_Write_Read_JSON.ipynb | 7677 ++++++++++++++++ examples/Filter_compensation.html | 4591 ++++++++++ examples/Filter_compensation.ipynb | 4416 ++++++++++ ...Making_output_for_Tektronix_AWG70000A.html | 1729 ++++ ...aking_output_for_Tektronix_AWG70000A.ipynb | 1495 ++++ examples/Pulse_Building_Tutorial.html | 1263 +++ examples/Pulse_Building_Tutorial.ipynb | 1223 +++ examples/Subsequences.html | 4581 ++++++++++ examples/Subsequences.ipynb | 4401 ++++++++++ examples/index.html | 341 + genindex.html | 743 ++ index.html | 395 + objects.inv | Bin 0 -> 4152 bytes py-modindex.html | 357 + search.html | 311 + searchindex.js | 1 + start/index.html | 461 + 90 files changed, 70464 insertions(+) create mode 100644 .buildinfo create mode 100644 .nojekyll create mode 100644 _images/examples_Pulse_Building_Tutorial_11_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_11_1.png create mode 100644 _images/examples_Pulse_Building_Tutorial_11_2.png create mode 100644 _images/examples_Pulse_Building_Tutorial_13_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_13_1.png create mode 100644 _images/examples_Pulse_Building_Tutorial_15_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_17_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_21_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_23_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_28_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_30_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_30_1.png create mode 100644 _images/examples_Pulse_Building_Tutorial_31_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_32_1.png create mode 100644 _images/examples_Pulse_Building_Tutorial_32_2.png create mode 100644 _images/examples_Pulse_Building_Tutorial_4_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_5_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_5_1.png create mode 100644 _images/examples_Pulse_Building_Tutorial_8_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_9_0.png create mode 100644 _images/examples_Pulse_Building_Tutorial_9_1.png create mode 100644 _modules/broadbean/blueprint.html create mode 100644 _modules/broadbean/broadbean.html create mode 100644 _modules/broadbean/element.html create mode 100644 _modules/broadbean/plotting.html create mode 100644 _modules/broadbean/ripasso.html create mode 100644 _modules/broadbean/sequence.html create mode 100644 _modules/broadbean/tools.html create mode 100644 _modules/index.html create mode 100644 _sources/api/generated/broadbean.rst.txt create mode 100644 _sources/changes/0.10.0.rst.txt create mode 100644 _sources/changes/0.11.0.rst.txt create mode 100644 _sources/changes/index.rst.txt create mode 100644 _sources/examples/Example_Write_Read_JSON.ipynb.txt create mode 100644 _sources/examples/Filter_compensation.ipynb.txt create mode 100644 _sources/examples/Making_output_for_Tektronix_AWG70000A.ipynb.txt create mode 100644 _sources/examples/Pulse_Building_Tutorial.ipynb.txt create mode 100644 _sources/examples/Subsequences.ipynb.txt create mode 100644 _sources/examples/index.rst.txt create mode 100644 _sources/index.rst.txt create mode 100644 _sources/start/index.rst.txt create mode 100644 _static/basic.css create mode 100644 _static/debug.css create mode 100644 _static/doctools.js create mode 100644 _static/documentation_options.js create mode 100644 _static/file.png create mode 100644 _static/language_data.js create mode 100644 _static/minus.png create mode 100644 _static/nbsphinx-broken-thumbnail.svg create mode 100644 _static/nbsphinx-code-cells.css create mode 100644 _static/nbsphinx-gallery.css create mode 100644 _static/nbsphinx-no-thumbnail.svg create mode 100644 _static/plus.png create mode 100644 _static/pygments.css create mode 100644 _static/readme create mode 100644 _static/scripts/furo-extensions.js create mode 100644 _static/scripts/furo.js create mode 100644 _static/scripts/furo.js.LICENSE.txt create mode 100644 _static/scripts/furo.js.map create mode 100644 _static/searchtools.js create mode 100644 _static/skeleton.css create mode 100644 _static/sphinx_highlight.js create mode 100644 _static/styles/furo-extensions.css create mode 100644 _static/styles/furo-extensions.css.map create mode 100644 _static/styles/furo.css create mode 100644 _static/styles/furo.css.map create mode 100644 api/generated/broadbean.html create mode 100644 changes/0.10.0.html create mode 100644 changes/0.11.0.html create mode 100644 changes/index.html create mode 100644 examples/Example_Write_Read_JSON.html create mode 100644 examples/Example_Write_Read_JSON.ipynb create mode 100644 examples/Filter_compensation.html create mode 100644 examples/Filter_compensation.ipynb create mode 100644 examples/Making_output_for_Tektronix_AWG70000A.html create mode 100644 examples/Making_output_for_Tektronix_AWG70000A.ipynb create mode 100644 examples/Pulse_Building_Tutorial.html create mode 100644 examples/Pulse_Building_Tutorial.ipynb create mode 100644 examples/Subsequences.html create mode 100644 examples/Subsequences.ipynb create mode 100644 examples/index.html create mode 100644 genindex.html create mode 100644 index.html create mode 100644 objects.inv create mode 100644 py-modindex.html create mode 100644 search.html create mode 100644 searchindex.js create mode 100644 start/index.html diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 000000000..254fcd7de --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 8b977b42623074ef910a560b4983cc9c +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/_images/examples_Pulse_Building_Tutorial_11_0.png b/_images/examples_Pulse_Building_Tutorial_11_0.png new file mode 100644 index 0000000000000000000000000000000000000000..0ed28146c3cff4877017f2285e4fea57c56dc38c GIT binary patch literal 7906 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 G*Zu>OV4N5L literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_11_1.png b/_images/examples_Pulse_Building_Tutorial_11_1.png new file mode 100644 index 0000000000000000000000000000000000000000..bb3ae4b002c0c418131b8ceefd81ae7007c0ff07 GIT binary patch literal 14367 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 BIne+B literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_13_0.png b/_images/examples_Pulse_Building_Tutorial_13_0.png new file mode 100644 index 0000000000000000000000000000000000000000..913bb112d74fa3f38901126ea745e0c8f9615ded GIT binary patch literal 9584 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 literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_13_1.png b/_images/examples_Pulse_Building_Tutorial_13_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0a26b1105e30acaf5f07e65486e45567994ea163 GIT binary patch literal 9611 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 literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_17_0.png b/_images/examples_Pulse_Building_Tutorial_17_0.png new file mode 100644 index 0000000000000000000000000000000000000000..406aac3b08a5d4cacdf6fba2f83a7ae8ff9a31e9 GIT binary patch literal 19146 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 A0RR91 literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_21_0.png b/_images/examples_Pulse_Building_Tutorial_21_0.png new file mode 100644 index 0000000000000000000000000000000000000000..4afe6d92c7993e200736244dd350c55119f7365a GIT binary patch literal 23661 zcmb501yq!8yY4|yP`VqG?(P&Mm2RXYrMsmBsiCABB&0)HT7dy+kQ^xi$)Tn5JoEqd z{`THyopaVYvvfI2hBu%0j_dl}6Q!=Ifc1p@2@(<#mXe~ZCKA#EC=wDfCHf=qKV0|& z=HMSu4>^4gEoW;FZ!labartgPS_UjhTzdvl3kY&1#gihu-q*ipxsh`GX9; zB&GcvcKUCYKfaJE4x2M!`9Nd$md>4&s+^QKbfypD`wA*vIyN`4DxKbDuen#+mF4Pv z_N&PCgIApkC?(Rrj|8tD--i82f)Vc&os}U`i2pfECnUhb!%JA72@mj&znd`NoA%>AA;!p>t+YUOTRk8$aiEAtq%|9n3;7!fWS{y8O zTqH?dCOr&bBz*Gy;JmP;1kI$T`5VJ<%v1C&pX&gmZn|bt(Ru^EX{Kny;5#}&^g-=W!kUz+iV;hrs^UwXwe@?E(GBsx$O6OX5NR( z$ADkfjFRBy4j#!8Dy*o$aCCHhO;qdAs>U29^9<>^r{`(=K~(=*WdB;(Y*JDZ5>m~q zp$C(aa!Tz4+Ed9US1P!HNH z678*cZ-;CJZ!rXUN96R2vyu3IQ&;B0}t9u^J z-Ik*fVvOLk?Af=yf8#VP;8b1u=9_ETTIdWJgv*|@#=<^_PnW>DWWKrbp;B*=r>+1Ngh}|$ z$)Cp*&iC85zqIx=&Xm74x&is~pTyO@`R27S5|eKf&h4%<^BUL@DA*Bng{N7LO-7J3 z35^fiNrsQDD|G&{=^3y6bLlqwMtcHl48eBIVL>s3?^ONWbDbn8j@vD!=KT7(Qs?CS zI+-(y^b_fCPEGFx0*|6if1l2p_9rsDdV*hK2Ze-Go;#pQ2YZV@LMk<>mrs*a{f{Fa zi`_Hr6yH4E*71(1)IX!4gX;E;q0!}f$B-KyTtM-~Xg0HDz@{&%+xENTgKjLu?{EGd z`iPTKQlcVV_)C5@s&0ph1o}USlI?VYaAY$>w)QguaK}9@jsC|@_At$-%QLfQN)Q+4 zUnk+@UjhAqar{N{|FE~RlH3PKMqV(hy7e*1Yj&s3%Z+xZ61^;a(iLU4teKz_FN@tG zRB85KtGlK4udB>V#XhM)?XHwOL_#Q#S1qZV{OI482l^{mlUJ!$w})lk;cD2Q0}>45 zrI@!9fV-@hoBroAxtu#VEfl4e5)uN+V}L_6fupaU=35-d(x{jlw7YeJE8ic{uXo2c(GgAI;z;IVuvSo?Rx2 zh(-zF|JSgEwij7yqY}}FM@=Tnjvvm9{^b{y%XRth3g?iKjIN^r5cRb0W%%1=9&bM=+wuPAJ8LWyOniG{6QiYp+r9wmFF( zTl~I|o<1($kgLB9AE}S}!ZrS3X9Kz=_Vc|yeYT$|FZGLq6)E$hD-Ld3aVs`FycD!t ziAY5~8Q%AubO#l!%2ON$QQ=nhnvaicz0xQgHyuNU;AtH3D&gEeJ=@2j473HjP!Q%l z1l)FntqZj3taMZyOC60x58krtQoJ5*LGscIbGXzWQ&N7||3p)jmX;NfHWY;>R~{>$ zcn_tDNJyflR!hR)Rtbt_&Q|Jqrh!XZ_Z#gLBo0+XB5D0d{}^1mq0|g^V*VOkrq;i% zx_;o~!yI749;g-pCC56-Ag7>%oKh4$u1>QG$)DQ!mHJkE!^QKjxiDKtomJr?4V?(V zSW67ZIh2UKyB9otQ&geVG_0{A?fJJLX4Z-~WBki1C5%@Im;0AHZ3!zUxq2q&`IR`xq%#&q)We+AAdnsB`<#qt3%Hu1{rUj~SC5 zcipUWopR#iSt$~t#++s+bGTpIs~hNKP5Qxqr4A(8DG~DgymeKD)6>$fF_#U96&DwO z3B2`N01@x&ek=ThQ*|jSF;bQvVq^2bI=X3=t>7swbxz1)hC$G%+oyA{yeO5P4HsK- zJ+<%OP0*vrV0C`b9yiz*5#vLlCi8+CwnldGh4|Jct1lw4^#P6a^aP517MZhD8n}3Q znIE9mv+UL_FMBO;lOE>;_Mkgpmss;eE71~%GH-od%;{U$f2*abbd;iJ62(^2u6bZi zD9cOSGk(;g$Y?#jo*iPaX3zps>tU3MzM~htP!3A;Jn6vE@Co4~Nl{dujB&>{oPY(4 zik_%HsqSVP%4SAR^cuZi%`GDbcSNUUDPiS~#*p7{xag%X_)UUlL&ALuhf%NJjR)<% zKzg3WR&Kk9Cx_Ql;)jg%!cmgH%BaL(*X^hGNdTtTXdF`Ert{aGY{3f@l}zYN5Zf)gfA@)ldjZ2QK8d5T{U~ z1av~cs-P>2F4ppypg`0Bt;h!HcK+O!CA!ucogWEGBE4DV`5)M_4YySeAt(n^$)L_0~*x<$cR7XH8?n&iF^f`FPCWCA zeO+e?4L=&SlA(;~&M(*}gy641lk}4-`THrNM~YQ!G(wn1TlijY^mn$SXqkgD_Yt!r?QB+L6uDW$!r8JJrN7N$+)E zi^I(`F4oMS**hIw*Eq9Z{@lvbSlWELfir;tI*=jE*dMV{<;bik{vfr%6uqrNCP_bH z`670=F(J+%%-p^8%W=2VH2sW+%>$HLjLV1*pF*-cLgP$-)u^Gfp2ukt9zVGpG02sD1z{-hlc0nr~`ZdIC+)$gzt} z)Kp@gNm9MgV4b9CcNM5^h@4C=1 z4kE8Lj#g@w2NslEvbR)~ez1O@F!gw@#MpBBK(DyJ)8k~=Oq=6GG^H>-Mf+84!j|u; zeiI3`E$LRpj=acz;}ATezPnJ#At=o}n$mdt}G}pOGt_kC(I~qs76w#QiOa+a(<_w%m2>akkp>(#Usue^*5X+1r?nLMtteF7_$ zvC;EKlF!SwE`lvYo?x91B?crxAE-G#pX9xGFp{20`l=)MN!&BM`0pDocCX{yNA#AQ5o3Ylf_sm>ESmldU>YZ)O<*U@B$(4OrtMhh+b6BrvCr|0I%8yi!B zKBN}Z&ILJ73)9QEetEWkHm4t4arF>?p0cL%&PH*G6<&o!c!Mm)pr(Rk9Zgj5G28g5 z5<1p-p&{9gtJA3w|#;EI5mzi+OV7JMLY^Ce!% zmGft;UL6U<#U>ejJLOt$lH^_FQaOF|QC356gqJ%jeaBPX>$?1nx6T_z(UjN3HlqkO>m?$ITdh_8Yw^}Ai3-nAp&`%aM0BSpj;a>4#G1?*i8e}8}2F>76K2CK6& zAR3Gm_Ki>#_EnIUWt4wLk6&jMmiv9*A^E7M@CB}%bjiY`NjGmC4h+ETuerlhBWn5gPff|DuF4>f%wu$a%yEhWilpon$r zJ}QECzTl2SPC;=rT@y&;+<9sJY|z@~O0uEA&}O8pt754c_f;M0 z)k_~Zxn&KVMNb~~v^+-1-zZ}V`mExWp%-9nAd@nRO#T%iJ~cK&F^*wZpE+-1Wo304 z;%I(@Xxg_5<6U~Ngnkb&<;fZvQdD=G{qj$pAQe`Q=7j77t253^h|_j)S0B!Pv-954 zCKpjN4&9xpqKBkYt~ZRe{JgGK(3oDs|2`n*QLyIDZfjAy^W)EFg1O`Mp`#J!K!WAF zvu2=C9Ii%lZCEshcmMM_q_cHlovM$DZ|r22)MgErOSNpLDh0(@Z;oyAU}3cGeo{rB z%S)`(z4`T&`6}|oOIp;_e7!Y$+_CFd>jy7qeb!=yT>g#-73Sw7pD(@R0-d1f&AxZn zkOC~`b0qf8`p4{)5Vv;H3`&YbHMoOdkCs-AMbuQX(iLb(5~Ij+UTPKi#4w9FL{5ZP zZ|b3l+nnG09b2QhKccC>^Vd~!4GNN?4!kg{G-|;IS%D15Do3F-H%Pa)w{!Jxao*Mq zh1flJ#;T#lS`3hZiGq`N!zWK|Zq?PDwUw;;3-httF{{&;1hFcea-|+OWBk-dfI|K3 zr*KuwrItgIcXH2>^Rp>Wh2`i3xB30^MQ<9Xmi5$1sl16lR7fuCDSE3?cWYAWhKA8u zPOXodm;Bgtt6rygB5x72#-$Anh^bC`S5E(2ItJB3ai~R|)l%!~wy0|+%si~5)kM(2 z$i@;0^cI*zQFMtrPVM=`4a=4uqEbxMnS{M(#q<|GXA$4RnEyNLgbmxX^F!|wtv=Y ze|~Yk(muEwqKVTg*>CAIw^iL-_J8?RI6oOtQYW@B=0}{${ysk10NkS+?0Qtn+qELuEKgM zJeLJR8QB}n_UEmtukskpS@Pyi3#j9Bdv%H_@MKcWVkniv=jN!t)^T2tQp^kU z^t454g>y8=<++8QBy*zEF*ZkY7;|JYJ+T)HWu;LB5R{)ejI=~mO?h_(An%bLXbLa3p02N90c4fleR_tIdxr{)dF zdA~vY=?1!1Vsm%)$a&X&z(U~lUKtZcUf`8&`^9QBB3!xL-=1%_-;JIx1#=UO`AWdl zn;PIC+|!E2T6A4kS+B@x$Sx1*}tC15SBllCGEpw~LqTuV)&ZaMbttjHwQ6hz-Zw;~=9+-r2&3sn#ng z^8uF|QUk4c1vND|rq{n_k27&mL}IiS78Wq$`;*4%vL}XzF|jFygR2aii&Ha3tQ{6x ziLTG~UR`JSh{w4IvtR46o=_JxNX2Z$~mO~KNsmBaS1E}!@(F!aofAFx- zc-(gA6bk~qziOW3wuicvxWB5^$T;SU`1QadWlV6n$&uphU^(yn)Ou0#X3iphrSp2f zXHrcnRxX-&ylRZcMd!0QzqSVkJV|A8KVvbpUYn4fJeJV82n)1gQR_|6nxgdYd zA4{fR_XO?Ur(Jm8W2<+i`a_Aye4E{Sh6>kW!$=5 zrW~-sL77Ju5D?&Vu@ZrYj`}1Jzp|<-3~K&;{Ns%*H$VS}@bDA07BNa}Vam9N{rYZq zVXYF#Y)@#r2hPo!^wqm$uLsm049$FM;d-Ef`^90>FP($@41W8k4P~6831%m!GOszZ z`kzo%A4lGEdB1A2;cW-C>8Hn?&k3vxCPB;jO^ZPIIsy{eT#aY3NK;}Gq3}qf z%Th;!>1^`5>-1loD!5>`LD}MqkoxN}v@dfGnagBfM00G6I*A8*F;g8y)h7K=e!%0T zB9u(+NH`QchgQW?Sb>CNu6ct?T!E#V)x@W!t36xbd+Lje&lfJbnH?A8*JfC0P-13q zH1%-2-7i4H!MlyGzE5hpUhm<@)0(5}<$G$d;(M-6;eQQsePMfE4s&0SYub-r?$}8a z%C#R4vdwh{yfZQ15^?Ez3iLmc+;1cyAmxBRAQ^a=r?xB`kInh9gzq_x{9bjH1lm}m z*{Cw?9JcR~Ur;U|ajVcK#p#3nS-{u_O!f$3UUmTM8^@~DTTH`nk3{d+Y()2T}91}WHvPkIMt~% zZWG-5_BdZ1eKXq~dkKW}%Go@yJbuu{|IU54{~cb_6<1r!qayEn@mK2J-T&a-at{iQ zc<|pbDHUph34)V{PbZDUC<)CHbnK?`UU&~m zHRq-C4>HdX_Y6K$)q|*gD>39{ix`}ZLmg5%lyGs$#yX+-xZMymq{Tb(kq8f3+Mh!@ zdd6V~{+nWv&?$>#g+dMSi{GJNH!|(P$;vBC_C(-V%vKv;ENn`ml}tuqQ3xU=N7WJn z@DldF;KrsB{eZ#w6)EWcJSe`Rx|J0kf`?~2VCVg*+cdT;s&=aG@W(fYIwMq27=jCJ zIpp6MOZpgl^zUQZg5%>o=O7BH`^&LfxAm6}%bipex+9}-my<0GaZqUBq9QL}%`_v9 zL&;S|1*`M^_5g7#K1WcJqZvczeBe$^*bp{0Hhw+oqd+A8O2cMO_yat=ZK&Za^D7A5 z8Xa!pW8;282}d^%MM2u|0S9?2M~U$PpdZIP!$toMvaiH``(M@Fg%*NDezBFz0n6?t z$N9rQNopJXvOsE zRG_4Iuwrpj<$hE4c-b^eV)L_%AE^A%K;=&e;d+|q!A$*_j`%U6y=ALi)In}etDghA zp+GpOVNZgv98}XB41n(Q*DW&S(@yJmU9kJ^H$W~g0tV^tiZrIY{1xQL0`4)UCv?1$ zm^>LDIkZxvBO{MP!5%coJ0JOyJV9tL4*HY4sA%f}8EoZK9CNa=$ux+={P{&}v9z>2 zfkTV2ER3HZr*Epa)FGp$(>K?f08gVDVVsP-xcs&V8Vqam$;@7C?6V&xSyh5QcJ+Ck z$&rzxF<>tf;^G3x=~aTTy5G-NXucB2Nl&xx;qbe^eoI^Mz#JSZKTbXLfoVV?;zURa z)aTIl85JU)(py^&-Nat03aH3x?<4k`9c}3X$vIQovRK?Om z{n7|(KXjzS1@|$I3qBw%ANh!%ic-$J&D5o3bp$I2i0_KM1!Y#?S69x~y<691*~>G( zSC{+ZDQ_g&c0HwKHLwaOTI_fy;cBD3Jo%(q zbIaoe5G7gJuzY(>e^9RWSn}o^El(6G)SGnmtOU=(Q*P;K2g70JaypZ=Fc1Zv0=LH3 z;2tQIhkqY3~3+?k(-|e@c0qyE9*>b(dwx;8g7qTA2(<;&2OYffiOOzs& z^rYmlA04ypKuEZc{?NS!O4*mT1(B+>$Vg(<*`lx2)!3e%o_@sdBW=F33sRJ4{<2(T zWK?*B=ZM|Ce)_g!X<|SAS(N!sBZvWx6cnhDkqz3s0g$$~wuy;}_xt-!=pn&zao8Rn z9#8S3wv72{c|T^RGL#2)zF>;C>gd#`ugt9MA+ZTx8~`hymLp~OqXUlkAyVK;ANh2VI?(}7c{+>m&)A{{(q$O!(y z0K|bF5El)J$!HA5=b-;MZd7#xWWfvqj^6 zt1q|b;XOnSP!Pq;`+-{51wR>B@EMDE*SX;2uw3 zC-J#@&@9B4Me)Gg6vEI>qxFazz$trJGw@xZ;%V@dAI^RX9C(SD_Yr4E(EpgUyD+rW z&LM1i|2aQpuTPLrucgeHWb{Z}s{1dqrZ4lTcnP_Pr*plu*y`~IS()iPVL`I)Z99bbU@Z8S(H~5T@NAUlI({G=RYWRK<@Z3|z!g%fwX#u&^n0FA_&B@g!|1 zESjs6G+y$G8E6fSzdg}IkdU_a_QGZjjczYcrN52;zzv2S8}J9>8~+!UX`LScTk#U2fBC{{SFu&CMjB{!^Wp$udf+8Tdm}-=OF3k0N0DVmW|c1<@B>WJ;FF zht*0|R8(vRsEJc6h*PU|bWmjEhL#gY@<)@k7@gysM8Z@Cc**?h5`8?&G+Dsr8FME?sil(6Z@u zV~5==AGpK+$*HiUI@=oK9lVThbBWFvzRY(IsNt@#NB}je;t#Hnpi+-s- zP~hXl_lE)j2`PANGjwQu&@j84i0!zE^EIh7?(f^8zX6TdRQIP403x;2-VUX0wo7*G zsMb2XpgRDp2LiAVKd`At2k6Rlj(?u}lYH>3mE`(8L2`jvr46KRzQb%=>u36?_y=B0 z%JJbJJ_Qx&Yg~-|QomBoKhVbfJXvqKj(|v&T1~4K`~3Ki{{%qNU8JdJ`v1l?0cdD~ zF0i(Dv|pqI_a!6CjGgehtc)!0Kxk5`ti}iCVW+>Ano|w$(sBXkBPHY|srtijI>#Iq zF297pb)0T2b04{^`YISg!D7Y(tPu%Z?hSa1Uf052x+S+JG9rMKw#6#l`aZqtE9gJe z(!X#NXdPB;`#mYk*}{cxGz%Ae?SE`Ua;!+-4Dcu&(Qkz7QT)cRr#b+Tf*l$KCjj@? z-Ga+>f6O=Fza5GpnG_9T_3-RT$(M^=N`kfHTD*gR;bh4XB@uZ27d00E5uknYz&wFF zF-mxtm@S)#T7NHNqzPo{2KA#dbdbM;2TlngX*$uKWsHCN zAS)8jZbx$$>jQ5@ld--=G#lfXvZRwol+AM}&evfw8dxcW`x zP6xZaPXVQZ=}9fkj<4FGfG>AKz5i|XZ}Yd6>ZyhWS!iU29<^MLG-lq7V$-mtV4>}) zuZ@1Epl73#prJaTfEe~jx;H!qmZYdCdu-18GgC4mBVQ&lqi=~FOUgcDG2F-CHv`~J z*#}ZY3(YQhFXIEJScfYzj(eY=M6SZxuJBUC7@@wjPn1rm0!%#{ml}4AE_Skh2KE6T zecm@qySYieO{&ksru~f-0C9c_fwaA?T3U5nx)N4!FJ z`6DV?I&;lzrk;|xitw&7Tf;}r#%tnv=~mvY6Ln3-o=EWft*12$QIX75+W-`T5rIlU zg>6h?O}`glEh-hGp@rRahLRJrVcUnY(t|IxS-5LlttEz2>*J*yn!tO%j&dIB!wa+NMO9il zGtF$Y7c?};^em^pECt6*s@@)cF;W@A2eKcmt~Qq-JxcYT3QGG?-*p=|A5{WJg=sr) zVZ=HmQ3^syZDN-ch>Cij+n$_IO31@9I`L-XqH zgmiCzSb+xtOQ8>-_W7y)NEKo0D7CX2@-YV0uJ$4`xVX9!Ull9~l_Bm=qiDm`sM0v< zTNX-f4HBoXep1cJyts{2liRpN1r*SL{s9a6<4d12tfAT5TACw&W$V8V7Q>V>=8x8F z!-*JpA232ifSFs;64ppt!V_*kpIXLvPP#2BIo1{x`GYx;&o$;eCz0dt)FW-<1KyQ0 zoih1CQn*Bzr8y(q6F;Rq@g-#0XAR4L1;g~YmKK3BBSGNhkm-&tu~H&;<_2+UNz=2E z)7Oq(+Rf~#99fi9iCAA%Wo7&LmS!398b>WPPccTrkENY~5$JPJmOcAQ+wP^Ed|O!= z3&J=+xGo4^$#{rYMD)Xl)n{K@p=@=nVV|`Z+tCSbjq408EhLyG+N}dd;f##~wJR{& zrj7^0(YALKkb=k+Ev7fMuko^s!k+|9!=$ts z6Z!Z6-y%(0z3kQe%OO&&DtI0~$=+o0!BXZFH=AobJ1t`iggNjS;89a6a%6$eI?iiO z=e!zR^l+gplwf-LB*e+Hf8Xr|;Y?3m@WjiQqN1}i04d??nfR`UEYrmIZ1l={uG>nZzWB+@|z^qBgoJcrgQp;_qSo{k;h)+B@XoQN~gQP0HvG zc+nqts3;>!7^SX1CSX!aMgEvWN7@T|p+TsX#DplGbx3Z5Fv#&MC{JjvkrG@3n%_}A zANzi*@4J$>G38iAsEm2?TzT*sf40f`{N|-7sA(;gYA=-VbNBdolTF+h<8oi1Y2s|b zb-jxjfqG7wsarkC?#03kFZ{8;*tYWbQu7|L$A9aVe)N`>i@i{R|7@?kZYb{)Oy9^g zVu-?#B7Dv>LMcqO`t1_NoVu*8%!r%Ct147t4%;8d@PXkNvp_<2E)L4VAm}^&+WD zj3ok=ikfWr9VH!GaX@2fT#j5pQLCEe;6J#~AI}`ViR~$lo=Lmcsee9m)oObFrB%lC zD5+*;kbdq`n$}$Zq_nOTn@FcIvll^O7)C@h4=^*}|Cz4>a=Qa|8e?sQDrHg{l)1|+ z^T1%f36nU>IOz~2Cqf`fi#?gISXX=co5A+TJIdzfC!D541@3XL2v8P5?mDI(DoyT3 zuULF93Jr}5!w0D5MRtIa?`{#V8U(}4{CS`Yuc71NG|5J=8Gs7P9zPYQ4Z6s0^>vj_ zbfME+>T|m+S6^&bOq#1z)K4XR#hgq0A+bPs0EFTviQGkeKN<3j`k9qnTa(Jb5Y!G;|YlD+M84=+GX``3cY-C3prln04y_gRZVd9mf zeFcHx7V6F{G99ZBqJ9X!0z%09jSxxw!t7x>yqrF5m&!LsF{7whT!KDkT30bw6vbj` zb_Sjen8keSkpzMz7%2j$52H8Bsw1X3yL!qU%8GYcxR_(>nbN{R^q@;X2L#jj!~`;u z@V{uL@x>~YFsyjY(yJ3)f%VOmj?1qBI_0*)q1joI&e|OBGIQtFV}1^%JD2OqU=ip{ zQ&IHXgzp@bZ9cSWnaQyxHewcPIS)yaW>Ir)0j`JXv% z{92puYviG;NT02S^2`XzyMNXb)fy%7eQsFRnvBLP>e;3mKxm%N(VyJ%RPjOM=>JVoeW+Hvr9kBo0AI4MVL5li_#8omXi6LC$ z^fb?n6LHBmkXdP_Hod3}$yhA5llZVO-i%{pdCVKn-@-^apo9B+Q(lA0SG`u{>k5t=NL{ZA`Qq4i^u7)Te}YC#Zyth@o{l+WRJH22p+NvjV(%Hrjl`F3%deo~t3e17-5 ze%F&$bYy_~`1x!EHRi9ubhT|R`jg!Ug~m3<+rYd(YNh0oPxeM}R3%xE+$#?CzOFEH zAB+&Rw0GT~8mry%jgadZ_dUK?i(dn|GR@DS0Gl{Mg5DWlesVs4 zTzKNB^Kw2wW;QzE_}6~H(^7JC(7XRyddLGbOXqG-B$ZjMBp^?589%Itk!od-gdd9# zW4%a|K99OT?Ybig1itrb*WK(rMC$G+ey-h@FBiD93tsb%NWmK$*5apAS{Kn>7txh7 zdH-(+t6inUh&}nX6(=C6_L_`u<&_|lIm*-5?#L;ZUV4JPfR8I}<+b^~?^e<3@Zr&= z_$yUyk=pQ0%Jte>RCw(CUf+RWmJ*N>5aQuEm^w#SCuA1Crg#qWGpqyCCJ@I5* zy{o*7$Ldog_LFrl^VyJn4l1i9a}lnbHn)mojYXy2g7nOfYU4$EFEEiV$TNAU$kjUH zlw)YYDXSbxWAp&-q~7n;&L1p){d#LGq)#OZlFmIq;jHyJTK&h-xdHk_$rIi@U@)73 z1b5LV)R_+}A`dIton0?0i-4AQl8!^95I3wO)FK@))caG`e$#G+%Ce37_1?vG;}5oO zzv*S1JLcqdF6t&%{an}f2ChKetaSxeZf3CRXXN%qWYE)u)1%j8NRr_<6kA0(S+Uys zN!sW=O7ccbE0va}2L(m~{c^#zp9*o4i+n~h1rXybGgXG(Gv~h?1lbLn*x?~Wp&47r zcB8%$HFLWZ<6vs47@O?)hI8>?Pqc=@k z=Wnur3EA8vJ@c7PT>M*+u-XZwOdX*yP(NZ$Uc5h#ehng9Z{f$WS-YUFcJYeo1uJKr% z#Oo!pKxXSNoee#H)&-ir3lGa&@5v0p=|5D=BQvWqgTgFJZQfj?BAb*zGL--mOuc$t zS32#N8(Cq@S^LSxOyF`kZnES>)Tz1q+u_%i9d|xwA)T&b4}c((mZb31KmZF8`m=Sr zNx45lSI|X&4_l?5F*knRv})%?r2~J#?v|J@8jDr-N&o@}eWW zxS-_vA1gI9Hm>}#uNiqu=830EnGO2qg{9SWW}vp0y^ijIz~XJ*1Cq#=tuA|xxvOBG z=_hYHX_RXC$4J$o)NO&E8ls(|zJ=q;9*{}Nw{ra$gAMsFyY~NRnOr?!H|?ZEOrsqk zOgQRzG%hgzCaGINp_Hi2Q3IGK8wN=gSU($HG%Y#yj#fG^fXiOKYV9n_ZhwK&s;Xm{ z+x%V!i-%Ll$fQd$OsQx6v}YqR-mNGbk~vzP&Uk=?=`sBJQ+CoS5U5aqxX%Z0LKmPh zuC~IfdkzB6+x(MNgg~(prLWyonO;4I3~H&cMFyHwSzmbUsOlI>$W`xXs8-+4CPMh- zmm=$Y&(HSFh5RpXdpVU7%f)1B1$vQoc*7#lUIxzF|Fgrv{q)oS56~>$VX#B9sNLLK zTf5s}d%BGOuhy9>c0A^>(of~>U#p)TtfsgHpJ2B_ET0!_SgSNg)&Z zwTU^LjYAH=J6|oc3Ga9Gx>#jJAmMeUX_ITAi#qLE%pIqqGze=a@rAovny{F`vFxqz9@^ zSV^%7I795yj}D@0OB&t%m2bEsy_W+>dU|?riHsGTTepiVXEIzh{a>>a_YkOwzn?XE z$Km)M)DKdYZ99B^#{*19iwA)up`oGrU4at0KC6!%mfA(Q;KUD+lL}wdS#{EOnxk18 z)<&pNzUm-OdaN89->pS44hXs!lAoXD4)zHiQR%9A>b*FSgfb|LgoGlh5QBCs9#9Y? z`hff>gz1fxv>K)<*t;d+s}RKq?E-YNE8r?=@uR-MPX{}*{?A;SEz3WJ7Z|qqygVgebg78O^3j{WjHXYjJAZ?1z0JOZ*zSgoi7K0;X6IA9G7eRy1VZUe*i(r08OYhiB&o<}JqSOv%u9%p4U4kG#o)Za-!PKRP(Dn39va>(7@Qv;U-X&`^plqU zU$-a{RI_qP`wy9npZC41YMr5Qq%fa~R%I%CtxjE2p7pD4wD#)i>aejUfTQLy&&M8N zD8N6!XyqmQ5ZTD^@b7Q$QRZ4b?d>vP4t*eb15~}UArFk&qDS=91^vZKd<;}ffW>vd zdu?-n1y=^`?1obGyuFY;0zM%R&v>CXk7k3vey=5u;)B9xRccO*XNzc`ZoQb(B3fl2 z;pq^3+sN8qRKR^qUjCdOdzL%ihR%`+$Wq=kl||TpgXfYj{vYt%amCjEfae-MovMLn zwJMyko-8+WwaXk0sbVDVNl+|%84e(^NSN>DUBFW%H(4^U;qiTC#)*_3w9D2ND)m0{ z>ION+(Z(}Jg~o)I#)Gv`0`voj}@p+%0a z`WkNw6>M?~3-|1|`&UgNAa5gpJU z*7{;2q2~In9xMw^&E?P6)~*gb#Ju-=0Ya(?QOLoI7gW2#nLA85{C8P=$fw3orJA!2K=KrE}@* zorSy3C#Q**fN%>I8js9Og+|IT0>lkwqL{@&%)ac;0)Pb|d1UW}YslbK_}cC8*a()8 zCxLOdKXbUCvk^_(9R>wtc{nKm$&2X(qY5Gi^M#G5yEe3Zg{nkxa%ixgiV)16X0KuJ z_avGXj{olv--q4Yy&s}1cgJa6F`b=~;x~sq=%^1KsxjKx+nd$ubMw>FP?_7h)PY{AIR{!p5YMUsX0n0_Y`?sHB%@jSSH!{%WI=?YU+5u6K*8>jpLbE7h`-|B8PEf?v;G#Y{k(-i&qL4}=ye&{R-fOC-aa z;F|b}DSA2_OaQ_nRq|Kf<10dlCT(Yh3amyhGgHm>?+OL#p@}=!Rhv9?S#yILqfef} zi!KmA5MrkP6h6CIFaEb~esAuJ848g4Ql_Rum>~$q@=L(=S$jX*=THKqbPiqZnJEjm z(P70ES~W-y1R1Vkjj=$!9L_+?`slOQD6;i|`lt$`_y><@bd%|cvcv1srFuL&0Q@!bCuk{+LK`>$AS~s-K_XI93No z{WOSBja@Taag{Z;*@Ga{f-WM0e)!!QFhTW;f%-TS43BDY{t%Nd5 zm>k=jQM5|Sc7r4j@>EiQ#c<<{h*K}W+XpZy!1LA{zCIrdy8A2a^85J=e5_UNKkz+P zN@rsf8TUtPadO>2<^R544idawc{PD#*G9cL9-f91>y5ydxm^Hw{!1cgvJxUw&u2FL z!l9d0{K7>u5EBclOkON&$ga`c($aFND^CSrYG9h^*Dq_Dh1Qdwug0$Kz$3&j1Q*gy z!qd4ZH>(wUI-GILFV3s4jaE7tb!FtlRWlbA`7(CEA-uU7ncy}rF2K-Xlk&O5P073- zG&~)DYGENJ6b;Ax^7w>~F#t58HV(sqs1-4-YKX6sK;Vu`05A4kCU_F|gh9 z^o%`;I{)SzJSEn`p~a1rKl&YtvzlpNJ>Yjs)<{SLzO16nYp;7#@?1nXM-_pts;H>A zh~}iHr+-`NiEu?Y`YzueUR-zGze5NM1|%_cfT&0|ZX8oqz>WT}QmO5&Jex_cRL&IK z(HM(ROlijfwC6Dl;@8h^7Oai@|7;QvCJ&hWo6$HI6Xo>0=*0^@ZmW;JUyS0 z#}+g-5rZy-4xoMEU?9%Q_~~HIyy3&F$uQa7$!CAN=}_bn2o-vyVebhpEbS)m)V#(VX6Vq`K)*gXLuR!w1MWcjsOGi02iMQ!-bU zUNT7o9~|eKR>08Du&r!eC3S;)i_ur$qr2Xo&c3Bee==k^8wuQ{vRTA%28T)6I~vyY z?*ked0~$==$;z(jR7xdeGS`spksc}G(apq;r4!jCqxhY)Ppu4jHB%rLefaYgmzlZw z129(yrq$VWD}N0Acxw>^Wy%j`;3Y<1asjX#_F^hO z0eLZL70}pZ#Gr>bdjjr0+P|s8f$g5bWH2mg_kQ}F`YCWDdW-x!y`3cxP>tIs@mxSs zYc)5YK7FF}L1BID%f*B2nT(D8qOUP5hGsoUGlLHB<$sP`2)Nubj2+dfAkX5BA&j*yeum>tmcLGyRAQ*dpMcC z*AjnuKhx@ERszvAnee`xt;$9_=HPpj)18*(X{CMe!dUx)V(;IL65dQ)iph#9-X44+ zW@OT)76>IcF(+-%6-Z~_`o#fozc)^6{25>ymJK&Y!Mb+;>(Bcm3;hk!g3Xx9rlRzA ze(BHr4SAilOBAs|R~V)-B}3r^K)1f@_r6tMl~G`CO2N#11MvN*Ps%~bl<0qhD+NnS zp6jg2AmSxMjyNE;zYlhlk0uH>YRYzp*eP5Cq~*!0=uL)xbGhHfcn}}7B=J07GE%e+ zwm&N|kIq_CWjf{8u3=*hUIS=Q7i9d}ek|B><*lsL*p6jMh$XaB)F)|#r<8Q?aXWG* zX0&@5{MRL_#s)P;Y*{*hdkn%t_IvlPYDyANRR4fhO3(bX@KfZEjEi1+3SRY!pBYO# zviqe02l#XQ4tiscLDId7;pE#7&-D3&S>MIKxSo+UO9vKCu&Uhen$+5Hb+;ek)78Pc z{EGaN&2~GE<=9k2y7s43V=3`YS4J4y<>D^foO ztI{`5r$l)>DanDmnSFwj`v&W}rQm}{#&*N%Rk4#IQW+7e&7ICC-MIs%%bO_2pf`@x>o= zei6?hg~s{^&`%U*I9?`nlpWAWB2~2C6VNy^BgbdqP&1^5kE}mQ2ke$2Z12FgMqYT` zCf3v7B-jSfD#B2l_x+w;_HyYm0O?Jg{L9|a9S%fstBf=sRu=tfIJgw+`?`bo+?Y&n z74_{RGlpeDqSP-o)mziCRReB3B@}oJvX*H}YIMsUAmAOVvH_XV7A~AG^RQ}Xfpts0 zu+YAuWOn#-H2=|aAEDbx_!tvXY2eD}{F(Kc+s1JJ!cS zpv^y2O_#6I-vMiA$q+R;D`G8dffir|Y^8C|V=M?jnvpNey!7F2xACvf9ZO61`6wR+ z#LPYp!d-g2&_ml(V40f-p5Ye{<=M=|zB)`+&^~_P@^o*x&@K|seM*IB9pe|cX~rPm zziOj~+)x5sHdj53MichHc@>bA)>MtWsBERsbScRzAFfYrev?OlrDe#jk&_01`{+3l z>(We1zr2?Wc%hz$;Q|`0kUkQ%xESX4hGcwaQ?S%%L8x_KGG;f8kx4%wKpAcs0{f>Z zUeq}~*PK5~k_6~sv465XrqHh47Ky(B72aB9g}l9x3zXFBpth2^VrX zlk9a$RgbTAZ!$(xGDF+R%8zGLfF7AGaT?)aPSWYCH*T8l9qRo0VOI}8I{dH(spm!A z8H+TiBxR+ZKl*VlA*Z9X|Jm!zGv5tDAOol+z1`vL%L~@Mk1z^Q9X9i&a10#l+@1Cy zDylURm%A4Ts1ly_E6o$Wxj*0PEE4yfJ#qBtL5mm-0-WYOmJUFZA+Wb0+p`kkTd+Z? zcGntx;rhf;6i77&Lw)1B_S}lGU$P&9jCB_#K8go)eG>6LjQ>|=7cSAULknc4Rd&bL z_;Dv_xATIX)(6d0*?YjTfD`D1VPq9Kjj0$sWLD1wL^4K3E|H7$Y2F7vy|k3Ueo7im z@HH`coK;%_{h19U4fYVGpBh7Fj0}PT=I~z9L8<;pslb@px!k-}>m|(?dmleLbrjG$ zhPdGR#>1}IlM(J_@k?=BvdQa3v+eKRT^zzCEBxkr=kPzUSZqB|oP^(a?t{9pt4u2` z0tMWH4cg?@)jTA-1#<{?#b2%>`D|bVNp@Ibd~`TAFN#75xh2Pwu3cEQKp%;Ba(7qJ z)nx|L(?>yKnm(>UA1C$d)q>dH0d>n~tOmLLWVy(JwA=HJTl3M%yAkVIz2B@&b!X`H zn?63QuKnc%*td@^9dZ2A@>z)IySkC!+!2ezDsZUD#bS3J_z8_L_6EOqJO8Yp!2Z$# zIRJZK_EU}nalwPQaPY}agQHJ@2l1y9{IJ-NGu>c)qvM>TW%INL2S{4?hvHRthQqvZ zf#{Kj7cX>T`C7c505t!vL452$tnvgcUXnh>gpJL+@GQh1?t>RMLMs0HrFew_1`Y_KSKdcZh^)NMLG;}RvCP~7Xl+%p$-SodpEsX1 zHu50SQc_EPsLm39Y<9e%UV>n!)?wZ&Wurt7v9y+s-B!C>L zFJ8aS4?dzA4^qrn^TW12Nd<*?NP=UrddV4rL)DUQa`Pps58W!08n33d6YBBx$@G0$ zSIeX#UP$fb*KgGgS!QeTsMYF{TY2CM2d(MVubye(IynmtGk<{S!1pvg=$P{e;Nug1 zzP{>klzT+%>qD#XB@zQE%i>f67kBqZpkc5(Qz<*$P!>L63H0@IoKUicGpXLM8??p( zq`VDh3amhHU!NxEV#G0F`#F;T$RiGMD-oo!*FCb`F_NE}HoL}yLZxwtnhTI%h3}SK zc*~z9?R|N3EtKm2yt48PIU6Jf<>if_Zt>6!t?8Y$ku(TBFn4ZA5;Qo=rGrIGc?o?l zt;MlWz3qPN*?3aay7Ag3=uKrY%>jm1TV2=6WXohTFeF>-Nnh+q0W{$T$n#gBYSdux za_^;t%*@PuK9(k8gx!M@J8PcRby$4(n=I~FeNyS`{kANC8*;041gp43&(q!zm*(0J`; zuVXWe#kRVf%Vb^PVa}Mt)9APP!{gk{S^x0AyP^9gBM9|Rm_V&_*(b6&TCaQmG zT$|}F{nFQ$x;c!DVek;gUgS*(=z>;J`0{%krCk}nw##@ zEkT07SgG|+NoRquylROQ%`DUG70Qw4^RI?e)UdW%OG5w9K8T;0DPToo~DEG|lj)qUG-_QC|d=SU5l zitA0xHr3`}kZxTLq(vr(zH&=piyDUxE--x1u2;=D4)wz|B;@2owgOLQd{|$vn`@{X z-ebQY9=Q|w&!(w*E*0@Hf1T}S!*TBOZttMhm}HKQ;X_6{0#L}Libz3H7<8R=f_TUs z=p*b}sUnFx!4Qx0YS8WVLh#&xBz$5~ z4JvcckMoCDK#{zBSOw-{!RiO(!tGU5-|@bzYe%Z#9XlC%9=KAewyF8<_e3}9rQRjK z_5>MO)8drx;sUr4@j%upBGNmOx+aqm;nLa0hL`La5FkU&#%;IZQig_x^5M~phhQHK z56!8;Y3Zv!vrrTny|F#1D2_~uxQ!0a{QCO(JQP~hc|Q4Fl2PNx7Bat;nUq$!_KQug zFO798QdbnUJZddAi$XVxh=Dw}f3UPHtZ8*AQs4dpHby;k)iE^n=B+0%pr(-rL=+}x z=)NmV!h`@rg)LnfWa?rd)UTZEd;FgG_%gCXqbSASpuxnf3xWPYTg= zQvz299Ss+vRiAYu5QPSaFiBs3rc!!BH+8$g+T$h$CI&+ zZf@GhOeNpDgu)vpwV4! znTa@piq~I3W+Z(5-?6%gKkGl_^0E^L2B45R>pepRD3}a3qtv0eI3xbSio7LKqE2Rzb9lU5RWe9iSrDHr;tb9t_z0Ul$dIi%Nj^niX~)ND3~^J20U$fmR1B?_QV{ z?g5TO;Iw{ly*a)>VTH1_Fs4iWr3>*o-^c+Kw)lN>BREC!nm7Q_u(~TEp-~+;=0p=S z|ESZ=0ZMPcPO*0da5W5!t&(xXxx5Hy;0{?XktFj{3?Nw{^j? zO{8*jbMK;osyR&@;n!BPE>$@uPvF?jAT?>`3)CLFsQ6bhvVx4%p4O1}8t zHj_du1%zJ)gdw3g0{yeDqwJkrwjQ+b!!AFKP9 z$tq`D$L3y>JXsii0S?LDO?x@NQ7I$ThNo*5A{pScM2(X;ko}avV@Vtyn3<%Xo zV25IRo0g>)*5RvY#JJ&wHbME=y zG436gvE43R?|Roe>zU8|em{@lN(xfQZ}8thAP{63X>k<@c%|M>jib6H*Uz7guXX2X-b_7A6)3QY$w%Cs#gZX8ZrS zg2~avk~zUe%LUv8!AV-%6#_vuhW&c>O}NM!0x|B95f@SON#gZFA_fsJ6JfJZL=mNxp$h%%JUi^;v@1gDVB6RV*|-Zr42PccBHli})du<*Gu z@L^A@*^SPQi$I=QRp&&SpBglv98u)w!;4PsquGpWA$|a+{m^cB+w3P)rV&nyz;>JD|g9+%)$?vmKA_ zkx@~`CmX%AjEub}n|)R8dm68UgFILLaqH^pWcsL7LNa&pV>QmzPzzm0Zngs-Ed`D{ z;du7SYFOCVf>DL8A@b!e3F2<}?=;H(f&6+rn5){XU0tj)4W)b?Z1NVebJ%jE&?JB= zeEq+onS%GhCZnS_-PFSV2>*mz&)& zR7;n74>8?z9NR8%C}nqoIC(!x)TUWPtcLkZ5#5)C(eb#`nl$bD~Sy`Lsaz+SjSirD+b zx)Yw^{RVD4H1OeKRxFNEI_^_W4jJqd*x1-!m-CiI_X_&@M6CwQNz8`P+^c?=F+#Vr zLT4$Xl-SAA_BykiVydT^2QDSZ})aE;b614OCi5 z-h75Ya3ZMIuTY8jK6O0Zb@b&*L>wPIJs!~|cdt7c!DcFKl&R#B(obu$9?y%OL6Qx8 z=!CACMvcdFC1~mCf81W|sYWX%*!vXQit9|s2}=J-7i)@P?B!tG&hR8Q@LGQ&cMI`))AQ2>l+cwU-~w?>g^Ym^z_nQc)==rJQIGx+)j5@P+3hA^yNxT zOFOB_dSKX^6So=r@5YpX#(aRTeBUh`!AFVWE`t)~O; zzVdrI%nZJbl4NCL%l#WzZ#&D>`t*2r-4}S@SC;&dmZ(W(Hmo}QBB-_#4Ew>L;Un9h z(X5yw|0Cb)@tA;5PCT&QU9%Lv)Ba(0wqRS?TcO|Lv>C;{vDOt*?Mxm4g&yCG1`2JO zBfWV3t+KKYx8qtO-TO}pEdxW(-BCwOQC0vxaSZ(G!=WcTaXjs$OHKv4xo*W&NKz8c zJ%|-F5TmQZWk^uq!))M72nQF}M&RS*C0He@YKfD(H0^bq(&V&J`nozCO@yvqm;44+n;59eXicIF%VNFMB^2@DUT-SZk@W%}=^PUs)} z|1a#jSe!L~eE7zO$yfP8hNL9g zKoCRoq+(1)(s{pYG3eli(prj>qwg;@VMgFE22ZE57wM7@hv$ZYAi=@SU8>{hVj*1* zTV1nyuE?-eY{daR^4sR0Yj7k%A>;}A@#BYed0tx^(Z$7uHWM9elPUnQUtg=;ugT`b zg_roDE9|B1aoULH_iro^&7a4@bVnZ9+Y@5YJBU*W4vQk ze0X4W`_$Bw(a*@YC5evBFGSuu%8@|+{o@mI6dri&CY(h(jjQp7ba|+Ra*ut(@)tzr zRy5GNsi&@K+>Sp%^varQ4vi-Kh!>u(+mj!j`#MmU0Ah4(^l_Iz_&;rrH@%C-$rA{F zya~5b_l%ThzKe-9QH*x1*Me{qwFyyVdPc&!$R|7_+*N_=x0y<-!!qCgVgssU>&Z_# z;RfeTNxQl4WS0v^S6TInRe0g=BPH2YM$E!;U(29TKaxZ}$5&G+-yq&+xI9GbdC@>*fFu`A!)qpnvVP;z0SGw28q%nW?=ZoOQvEovj$a^hKY z;)&Dgn75EC@+!uZ_$t(#XjHGG-r+LM(XMQ2OBCp`kF!QG0n)^T89bKiB?a&l|hI0Jf)eA%RiSkx6Xb;HmLQov&9i48vMZ1L|KSpTKJRAC=4X3TsR*AkJtP3B1&!x;A z)s^r|7A8ORNmtSIHR!OhvzMTc@=HdQyZg2E%A#Sm#Lz4BZPbY)_bD+Kb1C9lqy3W__dLAgDAnp@;F8Vuu@ z1{`zu4}WIw`Ao02+CI5gh{+bV^z4R}eGOsIf zi6wI+RS(U41`|hm9if6}xZTb5FBZ^NvzA8ii6W4%#}?iy5Q^~d(CcdGECxYuwN(T9 zOzMn(rfhc}G9)mPSn)?IufEq9kP#QZi30h*wZ)L|r<@Uu`RZU3)YclD^vf2FG$MDz zWZGsq(5E~H@E2gyP%r^Uhtz*45tgM;ekG+a;7lLwrwl^a(7ET5W>;=~ zvrEbQj%V&3B~v+Q#+KuAm{M&XN1EMnprvCD=DMjd`1TTlT^k7w2fg(Y?K4$kg<{2RAkz-Gc2L8RSzc~LImdnAm_^FwhA7*Up4IK~VGZN&vl;OcaAL}=(%`4(u zuo5-$>sAK7%va~@U`U+T1$>?13lICn<6?6fJwC3?pk4ifd&P^Q{rEGpm4138CKY#o@XJbf4*sF$O*?D zbA&HTdZ*%lUGs>GbRQTP4IKpV@g`y!`s@@mf)u@uNm4_+uW#J-HzYTfMdyvAv4*z> z6WES@%ZR|Rjc-~0*kF@%AtWY{n1#P1lcY$szsf{iGUJ?F9gZ%eXCY?7?vBhf_VT5%6x@t zv(2c&=pnSjbI#NpqYw$n+gf#cOhKkSnJK~1m5ZazwSO`kf5@p{TsC7fb~|Bcd{?3t zk%8z^awlFbnuB&9(6OIoaMQeq9%U;yDawKI)5)=DrdMfL$&;YRN_@|R6dzwIyHa=0 z#BABgFgZ8N@rI6iM>=Wx@O^lLLjBO}G>Qm(;HKZ?YGF(TomfUWp*7{p%{&Uc@qX4m zY1H0VQeRm4=8D`|No6ozEyQF!hP;ZOl{xt{oVI@AW%9JUw^zqBk&ui2Ksc4Reli(I ztJovFUr-SCOkqFdfX1vcBU7bOpx|wkvc~L-ukuy1xoEvgn!1vvXl{vNyTuGmqKb?1 zist=Xf)5&k8BgcrMa_zowwphr=Dr(??body&DxtV$i(f}_1k^XF3{b{C55CvLkjx2 zDCj`)i#xCVS#M*{7Q2OoK=krXa7!-!V|&G1bxt%+!YpTjK=aFfs#Ml7ySfkUIQ{gj z`B^>WmQ*||T6SFSq7fM4&J!8q0>YOcsAZhp`^+ZnTI5(pJhCcU6`({Y@PsI#GriI> z+WHhF79*NeAWeY7`#Kejo0Yv?bG@oF&O(5VT%#Vo?0qeWxq;5 zt&Htm)xe(RK-`8E&!^Zf12AMY7%@R9!`oel3~E#}A7>wFeeLs2HR|Wyq<{5XDN+yT zJon;ZnS|M>P1&d>T!FTNI7cNO5h8IG9ZN5J z@2Fl{Y)k&NmDbzOB<#~q)`?XLSb&14Ce?e2#Oj_LXU6E$vQax@448uXTzgsSvmehs zk)`OYsnt6-&$sd?KHY{6<;8OzpYu)a@6jZ}(XexVSs4<@!tj$A;5Ja-&>yx^OSo7N zLWe7M!u8w6Y|h;4`bYezBVJ}Q2-_RzOoIIZ*H@5_BWO$uf}*Lcv57AhoS{BkVw#OO zU>Ck>VMBbk5zlU(r$jtiOC}lZsg>YTQs4zLdMgJ9Nmo~Ph*9?!WbpcD+to^`bN@t< z!24oap|7#@&<#j|fmN@?=p<$fCG^<6VKB)XY*AGf4J7)`1)8kU<&~xWP(@WXj0|~i zkrY!7yRdOK^eF;$T}SWGtySYQsG-z6Wl}+1cN<66dIIwsV-2VEXc$Q-;DuIWQ6O|F7;lm>cD~DcSQm?; zbmBkH(T3OGZG4iuj`rLcZaP_QREe>Dc}LXPx(Nj67O}c(?|0*@qTTT=vC-OZ^NJ2J zU)|nW-xA9C8Qu#l9fA`-I2X|c3WF8(o0E}$mEamuki8BUePh+wpqjLw#(;yR>7~vX z(x)3bp7U{TPk9wWp5qLQl}Qw<)(b^n;|jN286jkt!d8{UtCY zLgXv*mU(GNB<*hupY{UR_iIMKEFLIa2auvk+djxp^@8&p5QbQY1Mi)IIJE)Zm$bKM zOjn?ZCKiOQK3+C7-uy{(KIz5XT=6~X_1?}%)>uc982rtkAVh7cIzB0edUH7^=46JK zqkQvhV6$AcjnE?E<>7$M{A;}KWg>a#WUSRW0&$I1D#pP(3`((Dl6RNtE7%GH>AbGT z<&OS{hL5MonC%^&M+QJn;n=#Mao6So@?GoIitk*r`@8!9VQ_d1H+;ICy{>$^Wwu+a zM=mcfzdr6pJ?U2rz<{0B>K&F-UAiJACqs!(vvc$172`dB&wIo;Qq^Pgte+-Wt0Y~U z2@n$Yehrk4ul^0Euo8R6@nHmm3%eB0KrFqLLU1sNu{%@Hx6p6ZjIF?(=|f4ubuX@A z&yUZDiZ>}ge@fadHKN1%Vtio%d=Zk=AdL#Iw-i#J&M=Y;miZ(D9A=~_qzAiEg(H<{IkxWPk{CXI{VBF5 zQ-{T@*N6th%d{CwdoBWt(aa2$oXD_vPpePyo@Nq8-$+C$$#s(s-tH^V2(@=~z~|Ch zsiiLc?B{pMs%Pw#ZTgyR{v6y|!QGt`pMapI{xRRTF3WvJTdT_CCFmV!Y;3GuLjPAD z?@-BFzcHpOR3+7LM=FnIIU{B#YqeB_OnPq>21yD$qW#%#9J~r={rnrrzZI92W4^?D zQZ8sBozhIrx-$H1!%LB!kg6i$_gEWZAMH74SdC6i#n%I(m>1B+YLvOeCNFcaheb;j zZglI~KYzUw7wP;JV&b`N_m`pf*sQB?ibO~rbb5*ABLf30>dj1#FbJgaE`1trvKz6jKj{Mna ztizF03MZOr=NXuF|D*AHKA}d7Sl+}hY)y?phh^E^u-E{FyL6`VBcHz|#M`eGR&vmh zvrJ|1BGjd*DKa|$45fs?)P%FY?4r1DX#@ z@S|k&%sN;T%IfXW@>hIs|Ow@jDreA##v9TYCZ)!&@*r9`^91O84 zesMy4Q!!SQWy;nrSTX3{p~$SeW9RoaLGTB#gXH~rk?@C`J36Y~jEoGgwU6*DU|p8! z)V-04A@0qK5rU44DA+AGC$i5Myl2gBfNI8kITS))h=jhHzU#jM>vpq@2 zR2kfFa^v-p(l|e6RFqO|a&ubz{k+^}Et^I33iZ*i(9Kmh+P&y{T#B3e<)hX!1=whS z*jwDs4_TX#WA25iolxi-JApc((+l0Grl0@r-0iPVTW&x0U9Cy7WxW+*N>alnIf*kUj);7!`TH-Xw)@|) zoZ9v*Hx;{@`D2*0uc_z!k|ADKmzHrLl(;rsg1m#ipAM|!$Z(RhIlC&5p{V3dq^Gu& zNNTLC9ix$Nihx7}KZ)McN+?}3Q(wtOWO7>zX-S1pakA<}hHNMQ+Oy+MpKCxQ6%4oh zXr!31SoPLzxM2I9xbtURiwpxE+@z##yE>7`0D-;Cj!J^93wBNhb(C=zp2|<_E-i;v zqJxXq>8)azHy*4TXE)!0%6NWxNnhyEe%QRHqGLHBb0V|3x#_iE)ptDbu9A{r3>Gid zH2r0D>+QO(I-dO9JUvbOAdo$5LnxCx|0#6oiD`*T25oY2SbC z^fBSmU8^|$F}qBx%e6Gyo9~s%k&z@M(xWF-Wh=o;EEqbi`guh1;ny_g<5A#~ub%Hg z-Rl?6fwokAR@`rs>Z^qZe>=?49%QB1l+LYR6wEkO zI?<jwN#Lm-#XyJh^8R(y%QYu#Yps&sGI1|t;?&CdRqg{ye2PrALOgV)iDVLj zuu7w8-=x<4VB6u!QzV-Qin$TtANy=9|a_8HkG|!>gtwLp)eZa$q(grVsrB=xw zU+h0iA*8b!PVtxDS-kE#es5&xr;6IJynWV=pa&i}homs*3`{wohOJuaT(+0%*fEas zzQI>qakd*JDES<-dgwaxpf(VNMnpt({gb;rcKj2bv}yVVFsXoiWLeEngoP4XWjRV* zX3)l0KV-*cda*k_*W_yXJ?!w+em`nW;k*&B&Q}Po-N92Rbf#Rf|7LX%=Po^M7n-%DN7hTiDxI z5_F*?z`s}m35M53IL+AH+}D0Ks;SjgR1vhb3m@~rHiM%CF(0QIZnau zwhSFyGRXsf?O`7qESo3fv#sl7rZnhXb-&%nPBXiAcIY5;2chSZGdxW5?Tx_gwup}j z4FtPDAr{3}zSn%UIUWf~Uzv8znoASbOMF5?dH3nJHUCS6%xiBq=*IYOl4dxHmn(k7+$UJydBF#734h2RIhLSAws)+u@vR+ z6+98_6mZGAs^-`N4U-S!O=RNMj*iBg@gUS+R!Z&C^K6Dr7pS#}e9@4jO7Xc3=QBPZ zgZ!4W(DRSq{@o(F|I*G_ShxD84SM&Z5^qbU&W~5oTfaYXXFc6-V-G%V89qu>4Vkft zNLOXK4al4%x1UqFxw(M|PaHJ6H+(DeJva9`PQ*(=!Bb2TMMcz-QcU5k!+&+qLL$>Y z^V`;!FUn}cj-^&V&zp!$%U{rlos1ZSHAh3he!ssZXH@@fuJS5x73F=+oJA@27IKYR z1fsm!QmS`^(k#)GU(0OPeEO-6z>1DnYu#)3zO|!0QRk+sbHfRAPjlo~I>M1{msjq2 zHh=u|X)aCMMS9OrFCvn1vZdQXU(j4;T^Uu)t97s&G19&tPG?7p`o!vcqg^sX@ZP{v zcH--KL9R2(fCY#7H?@Aum*^B5ci*+_zb{N>cvpWfd+v@n5{!mGu$%76_Sk>mP*s@z zr69j?BBJ@FoLklM!OMQmN4m_mf#8HEe8=`1YFO$C{28-q0oCfo1DG3J@3>0bqJt)Z zzS!W{2h1;a46(tFcP~YPriC6}2oZps9QcD;bI6V1Ddgy!vN0!z~WK zbQtBEmnVHD>FZR5ZU?p$qdI;h_YvN=S&PfXgO+eAOyj{X5z&xdv1=zyE_^qJgXMc! z_rGa?5liaOcA*g&9bL#d%8C9qE${p5Xwe(7?vp(0zu~lR@WS`_)~B4GD~c!3F?Zav zjqq$neuASxLPgz#Wp}ofZAquMstQmNzhjZ5fV%^)i)l?(7MAP*T2z=BdJ;l%4*^*^ z^N7%$-Trb5Zs7eU2{!5~IyzN1(QKt-eur*@|Dh!OojLo-v!Fr!5@qYKu&J|O6ftXm z(j37lM5A^4#LH=rv0+V0_*QvfPw}RGlqy;8Ii&G#ZiLw&m5gj@{Mk;KVTX|EGF4bu zP-78eQ9-U@Y6 zFgMHAETxEydDlma`YNOm_~A~y*qZsRkNMGsAar#Wen>k`8TEVh^5sj|thKSVRaR08 z0-iM?j}zqX>Zp%*J3S=ut|71+n3+lcex+F*zmk^97uaDIM4egj>N2=-X*{42&9f;= zAm92etNMk6(RM7kjtasYZx~|%#)kN8TXAU5>F*es-F)@SVA3@hY~T$$S#E7@UBfoZ z4{2QMpK%R3!Or>-oOEA=h^VQlt--9$R8HIPR^zz|h~nb;*q-LF?HuV+ButWnNcB8F z_K|o#43E0ztpv7ur!w#h&5#T#abb%RHmPAJvN#Ub_0)c|AF$g1_@Y*lQf{X`ZE~ka zT~q&aX+<;rR;ReQkE-6f1W=!+ zy|IWZ>Vg8RKDLLMBxUN@^N(X@uRYv*q3@gl|zz7%KKY88o}2 zpP$K~z?FMRR+EmPG}jc>u;qJV~OjsaBaT$v#pM3A4G4f+WZ;U~VrJ3}QSBur#H z^RnfnM@CnEgsI+GeVd7jd{}R;S&5Hr!Rc1Rq@@OpiQBS@f_#7jX01BEksC06=QNN6 zludQ!P}sk>%<3*9OjFsBF^;2{Ljx=SHZKmJXRN*VKG!o!90w;oxquYV!il3+)IgadFHt z9bBX4!;Yr_NGGsr^Xx^!emTk75|~klM8eE5wL%!KHSN-vF^Q|J3VpF6S$)mtMiY3MMPa!e7 z^2op*#linA5_==Yg~eI2rC|9-z`?cQkr4G;rFTW+ypI!#wynypP3>a@!OtocYt?;L zpCv`U=-v|?X%c2TKw1K#dEX;&>@yzaz?db>Om&;YqmFWoc`%sn!46DNg0#kbpD*OV zB{2^zT&8j8cHd-{&*(!v51rlr=^VF!~M9j&y@wRkc= z{}?nQK|vn!ea;Tr-b>-X3=$^m@`jEJG3i`9UwgJR6iLAaP#x zzam3>o!!**7HDg8ZN3e#d%)3Uv8s-bHf&$7C(LpaAHSpCJNDW$Vm5lmiXNK^y!?Pa zDVC%%aT}{8>ZP?z_~>uLdM(5xxB=kbxeg6GVB8b*cs=f91$Ct4nwu~A+E$SpfMXXOTstT&F5V9d0|}Hh^Usg=KUbTJQU+I` zUW%kn?fTN^R6%@V`a{!edG%@2#uJK7fK`V9TSo=ijjeAE2k_^sJB?Ore=_0ZqPr0; zz4L8Efs|~gKdYIK8+XA#A3tXSNSth}=jC%PJCFJ5c1$US`3^0t7vS26v2T9A(xaut+rK@oc587yl zhy9)6$Y=!{z$|uB0!YmS&WYnu2WNcmS?(W}Y0Ha4fg>_@)NN$>4%jA_yb;6EZ!NjH z?Ti}`$jKRDte&juO=JohIl4sP#!0NPTNIMwjbP36ms<%rUTV;JD6oeg2r5ZAGpg4b z^W6AVE9B^omHjy6s-*OL#HBwh%!&`z7v|Dn7_hK>$6U`zfzn1Nyj@Wua@XdR#cBZam&^JDN2lkx~)H9G%$?N{K86qVp) zmgtm6h>D9(CIEjMEW8sSgl>%Gh&l0PG_D0B!jvcD$NQT}gHY1fkf6&;kH+QO-9DPv z&wXxvAo2;Lxu@}eb^c>`b9?9l%pn_0UMeao8|;`uGTh57zlaR^`ZKn*I@X-$`bZUS5-r=;W! zOlvL@x+u2xw*>nf;Pk9)Z722Rs$vm>gW4S15FtEwtyCq3O*Yv72awWmL5*8)ECgD% zZ?2&407m?g>4 z^2Xhu(lQtl=o3;DAz|aR*)IRp7yk{e{Iwv!_kLnk0(3^Ne0j_^kvf1i#>v|q0AfPU z0ssh^xMRhqbH(dn$AAzRi)BkxIh<}R>!162Qp!JKNTak_^FZgnh>Ev!N1UbnKJ#BB zR{&SytK#SS<<|7HHIFT0UpTdAR?aTt@C@u;mAVZm!cRA&Ct!x=gou>4Php zOsV22aNV}B)8`NjChdGs-o9f)=6ke-&7XMFzKxN_W@$0T?lELp;z zBD8HL2BLI~@FNez2pCP(TPeidhyP8gNKTSL$#oVbA&`-fkdiCs3D|_7>GL4D0E_Un zcBV*ud3=zIc4uIz9g5#R<53+rF?4c=rG1Vod73s73=IBTs;*SO!ZX>_-n* zjHHBmi>LM5f1lEF<5SuHaVsfyjv&lY{@sbe$2^IDxe^H@@R&AL^DZt4&FR!IHl=F0 zT%%?{vtpt5e~~Hw(~mBGrM(HU|NCL=+78d;p_?!?#T^~V(&YV7_{U~kCWc8P7H4~t zfAJw+SJ?@JEDd)l=EbpJFaQdMreu0f6%+y7#hqqyJKb)~vP1#2L_GiBtmuCO(Q`#w zRJo)^K!f4H2%a~Q5HV&gyZmjjNC7s}iZLt5`WmefTbIFF@Ur^rcsh1ec#JIy>RxS} zx2U<57h)hiGMOaE{7p|M<^3QJwg)RUKCm!oa%qjeP_!XZ&kI^S_$HT`8AWzOB-XMpNpF&7n>cT%L$Sy}#U#gi|( z147yAi&u9}8_Nwv@?kWHQLkVx(eZXQbIin>LS&qA&I$Qpn8+3ZHKv_{<`10plOU4v zqYgMSeC3=^Ap2sL41F~gu;z$BU|i-Q^t=z=x%NnckInQutS{JQA{>1;p06b0eL9u5Y zxgw#z!oFfzN*{$&K-V1y2rRnwwb^^QsX^BN)mgG;12S{dC1lV9Qhia=ZZW8(CgJDa zZy62O38v_2^3#3WjL)8-gLrKJN(HVo?C zw_|7PBJbl&DnD49^-TLXpZ%azXF*hWLt0f<__(=mccA^K5C8 zpHJVitLhl$!YzUsC7SnS*@)+Q;#!B{$>58$Tg z$e}EA&Z1yMf0X|9Nk&R8Qe(yMeSe1_P62XUnS@dodOBjjF2_;Hfx|LRdBk&>xUsA? z91Uj)cb&bug?*56QtEO627uhk0xq)ZqvD_8Uv zzdc}TJ6F*I0FVwYT&H+0ez@=JifU^I*@o{MiPM}W-)ba0BETr{?BX!V_%7#ju#NGv z4%h1fp(N#ngtEBdpmU?_fLc_V;wLLCv1R+P$e7YcZY&A(Hk#-zlR6VWGp;vsvWiay z0-#x}Q`29hrCSah>gLh#GZ2#(^K|UWUyp}vknzMcFj26NV`JfT>#M^BI%?X@D`^tY z<6F=j>Z#oR^b7gx$p1#&NDUK8@3w|eQL;~>Idirh!mj1z;lysV z^rb_;ni(|+26j`&xQjcgJ30swH(iaHr4OG8@o^ez^7}0xK&UM+r^xAuQDLWU(;@D# zjnkohG06#kGm__U{*b9B0%$H?nm~^Gs>6`*(@`NOrG^k-mWZE+EI3@D1=ahvupYH4 z7ERkT;sWGFBy!*mLvyO=@?Mr4%sF$Js%vG8%w2Z21T79i(Zxr>D)@7-Kj*;&e!RLf zoMIe)+}{_3&o8fpyPHTXMvJ9T^+S;zJXoGi)x7%95SeBME&|!oEt%HJ7X6bc z)&i-xb7!aL)VIZ|r|O*?8T4=6_y0&1ht=nf4?&3 zjH4qdEF^ElWF&0MICCMn_oFIUlp<%A>=Uv8eHSa)cl{_{_+Ck4HX)I6DQv^hlvl8T zw$xUQ>e8!|Sq@6|x9BBVQBQ`MfO?&_3?9agZLDI2X+l2Fc>mO9xtz~Lu9`auKP7XF zf`gQ$7&top>%LikIK_0A*95KRqb5oQ1`5DPr+3v>B1-vi0JGw%VDw!v39E)ok`8bw zFP=Yx>8`$4hc6+7!U2Tfg@mo`UUA1)GLR577KzoD_2?!~!`p7qoQ1vUW7#G6r6Z8X zF47x;Y%up*DDM#&Ged5=jL+uPq}F{HCeS3H1EtkCm5pU@G+ORaZ2jao3z!~*CwP2a z9DWt&|G!~JvVOp%$W?T-wznW&NrMimmVEs(+1IqgE;(LZv>UEJLR%i0QfNh`#;UBo zRK+^9s&vC&*P1;v+l(3Y$=vhJv+Vprp;FH`A1rEIET-F;UMmZY#-Tgkm6YTa_ROXU zw0Fmqr0)t<*-X=aKn)C?UQ3>*_@qz(aN6bg+c5xok+TthDu1655P!pX2q~}_ z%reF-*`KJ!W;Kkj@j?hJD#4{Vkvny5LA`SOg@GGC9~rm%Ry#&lMG)?xoMa%=^6lY{ z+5wlaxzv3XtFY|{9{@mU$>of{x6|v2m$j{4l=X}2(|t$sULQu=6wB`W6$v!h&J9S* z$fc(jYe7N`<|r2qcMnH@TG2%xRcPw1e^&n-`nKF7G&e%hFAuFia*$sa!9lSEmtTs7 zE|c0Y<~|4r`D|R;eLG(nH5yB8HU=hHdyDAkpof@t=0kM&G?oKNxR1!{IgNPZg?RH= z=XYrMz}zk#m-SP*|0f$v&))0%1R>5NJGz*tCT-`(ht(&dS;IiV%z#^KNGGUbkz)W* zNsYzu5C5y>)!wO>t@;6?+D3VNTl9-Qo_VoXR<~mzBD!zc=}ta> zO42u)LM*2%Z&B=N8v)e7b<0OEHS~xt3PU^-6X=@%R@L$N4{U4Iwsz;5{6+Hq9V_Lu zXx{*27MS?^6G+eB0i*ACGidm8q0aUMCisI=DtYe*-svK(cK~kg?cB+1q~u&oKg5{R zmv|lHzvQyEw-W>=jt%c=^cQ&A%cFr8_AQEd1&+3@pO$H0Bv<7D)0^O2vO=I$HG7K4 zQv=?5eD&7YE~TRqQN89*w;k6{wLkOdPlOTBoU`n^o~Ik(CwN7n8_LHMk|*@QyTw(Q z?+vWQ%+3$o%P%0`N=v%|#~xi7a8XKEE?khWg(U&2+^Fn$Or{DgoRnn_-czD(*SsHh!1P6nFI66>-pIP{ zp@BevP6Y~9cmM`ra#X3;gaO<7RSg1FuAV}=NFOf@pRhsfZ``jQJPsAUeG4_C6CsS(I&8mlSn@j?ro&Z0z4zBYC39xLX?$1LBwN2gM9Wa#`8S`^zp+SleD!Nw zc3y~Rd&b;!wzr&AW`mZ3%x1;X8*epFaG;1p>8G)pC;>UKz_dHiUnAR4kS-`di>a@( zch-Ls?Pz~DdIq31LD({YjLlGIHLL?g$I8Y=_FsR3x&7Ltj8xSc6h8foA~-p44AiYl z`BP2TZm8+(jm@C3)f=QE+v;AO?={37bbO7s|8iHpGSzVdQ@QSLKcj?!16fD-IC0xw zx2Vv*A@N#c$FGF{vE5QHd}I4G@v{*{_YS7d#Ytxqh! zasq>yzB}#Skm=~^Is+FBX5PeYfKoJ*)pmcaTB~tuVcVK{7T?SL1jIu4^|fy%Bi@Xa zbI-EBwPN6;ec-Brks{a^H5?4;k7I79C<6dGiuJ!uYo!Q8>KiuN1^|cM_+b}UP462p z)5_#RI~pQnoWEG{ellZf1~h}>%_As2y{MhZa-;cPGs1@mgEByQEU09Q({O5w=rC!R zBcfGvmQEKRvOt7x61yDt)mcoQTZ2$x3_?XHz|+J60cvLup-B$3OHpUjXMHism7lLe z@4>G1>F0A*zf=VY&3I2zZrz9)x}AKlzGxzzvmtipyMu&9Kc&v@o>SRkHB{Hqy)sJI_!&8|{F~@ifybnMW;80%YNv4>zZy}R z7kxz>n*_^R>fX?>0kJuN9Mj^cuICwD2O5l7XC=e#aw*VrTXCuy5$@BaB5zs_&J*W#Oj$hsJFMHunJx@_2X2S>Y z^(on?;}LojTg6fk2GC=sA}f%7{={oaNE1XvM05tVgGQi%*J~lDPQWQO1yG@tl~re= zra?+So6QdrkUIe!(Phsx7b(6!--_|FZ#Y1FvIj7HX(KKsdH2|y!m)qWd!+$O8N2q? zE=7Zah-#xaf&0zpki7IuUMhS8C2FL^J9nCRwh3G#P}X)m;@KYJd)Nfu+uQ2}EH<_( zonh&?d@FBv&E34j>-=jWgV+<&;D9Wy&;1ktb8Mj8l^f{iSIgc}tGn?g&>BbEbnDt- z8ZTr)0QkXo-mN^sUPXxyMMXsbyMiOCDWp62uhIn&#g+B-qxcUS;S2#=b_^qVFwH0B z$Bx9z1ZZ^&i{5!aXlY%#4--)3Far0Y6ch*Hf=_!!LR2R(>alEZzUSNIa28lM*0dNt%Q{VrrjZ7Ux2Z5vW$IB+AcrtB+XR?pOFdU&>Bo}+^c9ofz@Du0?sU#XXla_63d<$JpKCGQIAP^RkNZz zpT^fkU$x^OBgR69XdTztw^_LrhpH~rT+|4GZDDK^sdiT72%34TL#^YF7Gtn?h z{)fpY>L`!jM+c(DGEH9!23Yx0TjAcX5Y?a%@g>Y$#VbGo6AHE|!*%GpiXe&ZXT3G% zZeD0G6RdDHe{Mb1sw?yUdkAQ}j5C{?+xuS)?>rc-mS?AE8{+EraZ{8~96dpS#RSPL zT3&o?G={V0boSA8AI!W)E$L-?wgIwSVz(DrhHmYlknx!D1(qjg?V)OV z_lA^{6O4f!a}C z*lX=sqh3l&n7p@~z|;L1tR9`I_4r35_8+%^>u!<_Kzu|CYG3zYDz3p;0PwFtd~P{x zes^%u=b7P@m{4>NuNn_{$|NlKS3YEKltER~{@z(!yXR^wQ8g+J zh;%Ku(yDweY(S+k`?}|UN{m+3KEbLOgyz}WK*%U={PAuti|=Gj@`$cIhh*I8wV1xW z4lae{aHxblP6#Cny~^Eh3zJoU$gX3kpZ7Ogo(i%;7Abs_GLKM`p786{T+4@pzFH>H z$db{juS4Fdp0GK#+~R5J*G9)pC3Jt>-FP;_e*$_t8~jra@#eDMIyy*zNWYh8DB;_- zLKnI=*XqpC9SYd_X#rWyfE090E@XXIy~K26&)qQ7{i?~fU1Yb)~6##zw?3#V6s z9O%f!e3w_hR}JPlXtpkR^d!DzbnvM5$}ap~&5>ArU*x;dQ+Ta=;2~vl5q+cwPydt-4Ik+H#WS3kK1V6sV%i(Ff1 za?j<#VpYJyZDikIK?o)UoV&3SHI2-|;y*4my~b zd+z(XuKT>s|Ns9RCI4i;rroN2v+A~oP56gHcsR*HCel+V22TthUy~IKp2oA3wJ^G< zC@kB|3Z= zzn$c_Hi)<-b~jq&_}Siy=QWfbY6>!5swAv%f{&>49j)3B9S>*P5XkJBEd74+#ZxR- z2B8vsdWXCks=fZd*z+!7-+qf$eR+TCrioj(LQY;DP)o2Xs;XMr+JR&;nRwVuYPP@l zhN|jeSoOa8Z~qQ=K+d}*t`UQdT56x^amPP~ZHzwX&}U`|{vPr3ioE!leecqz`I;Vxm#+$5?{6<3sOHES@FSRcPs9>~wAZQx@>y7V%j3IGyd45V-N=oxqHbqliQFj^})U@!2 zh(ntWOwb}aO+JRzRuiY&B@FZvOfiE;JZ&z7NXfBn>pR2gy!11>U8gN1o<;Y+Er49S zkkI>O0i@2)sMZktKP-Tl3%q59qUt$cZJC%B=7}}>56DMAmP^_YJ_((l@D_KykJ`va zp-PzbrbqI7<)@||=(pRHMN40kOZY1yXWk**0D+<^P6$->YWt4V?Q{HGelT^j!%h=? zfSIaK(<&+$!5nyoiK!ugiuNUL&p+w@hf5okxNQj&k3k#k>2PguRy;9%722{NR4pyd zjJf!ti^|a_HBcmhJ0e5qk2#=566vx@)cQB}rNt;pFRy#NFt;rU$=ovV^^{5MIS^`S zW_rlM2ih)dm$&E2;w7Bf`at{M{4qs^g3|UO6mwUOQxCzBVvFT+GpgRaVL@0nB@R8j zP&yp;0t(7UGC9y(1dl{NAY))qW_y%T$cX3~fbkRd#6lNVzFCe$9&z*-r_>CZc_e*-O^tA0 z-^z*$us0!y8B@mRWHh%0aCXq>O~Hw7KBeIyz2%IJZ~Ii*&)UmtX|6a&E`}+B7?}P1 z{ZUex=GyU9NFh%uFWrm!iDtZEAvza4C-vkQOu`c1&E@c-n4ZvyTrjvO94_b!%A=*( z;!vFUQA5f{tS%El8{@BQi={mt7#~hM9$ag-dnBx--67fGjvC>Ifv-RTbkdMq857bu zUi%DYxd#EN>Ez?|TD;z4(Uxaf8o=QPU5%eRGEhcMU5gu<>~d%3>Kg>8WY7 z**{6KLOByq6~t9M@Cd&gN-@L-T1Gi0oGTKQSBE`5K}7)Cp6B_GPV9%rbu75m>uC!c zXZ7y8Q&q_j@5bC|U4m)W;f3`{e=JsG;MAFE1vj#z=beL5Q5GE%9LAGNu41BybqoUy)yrHnSf8u23`Qe2rTU06GKR)G>nNt z`7T)8H#O}yFkemX)gOS$41>jXS4&=Mu`H`+kVptE&R6$PNOlNH7AozB4)P&Yfl|gO zJbop;yhv+J>O51O{m?*t!bv5OZy07v3>w@fp^X+u_^xWT&-j(F$_IYL!xT_^eMC&g6#klpHiD3Rv{fGRK`a{7Z>)SSa;Y9`d{*TYFqnoESZJq4^n*g@hfF9L1?k_*jp(C!# z%WvtA6~=0C@F<_LmrGfaq+*2#KcWG&b9B#qbJvCpqpjEFl!4kUpEV(V;bQIZ-H*c_ zg6X2Zrt<(>7%VNV&I)hl;N;v9H!i^BJ{jg=S(v%PeLkh+%;-pb8tJx!X}V*zL)y$o zx2V_9jxZNXxVgCqO-*bu4nqHD*w`X>?%r)*us{5|&iZ2d8~-@R!FXrX)!K7hv!{b| z(oQ|EYB)^liG8vPy1f%(f{c5vl9n^Qml(O*mtL1)Xw=Cl!2U9wXrQt8WbXCO+Lsa_ z;pzC>LgAh5yQj;71p9Ba4R_sQa~9e%j%;hjF5JCD2x)33-(Ckk$wMWB3PIU}fKVBK z39a1Rl(P@sJLQm$LY~dyRkmj&UBrQt!5i|LGEi5{js0TXw)k{Mkp? zxjPO5E@l7q9gK?A`}1d+>aPs-;;|BAss(zy=Dy zj=5Lhei;l;a43uk7qF8WDzB)p1n;Q*@4p8FcL{2uwFOGp6^`fb-L9aO28|u6Ss$=a zigCKigP0=}%*NW9FJX&efG!8s)Mds`eCY290K^Kh|I#FTgHo!mK{psEWk0E7+Y>T2 zps}m~`FjvNWI=`logf`7rX@R{4ZaBpXpk9Q@H!*D1sh7@hi`muTk~KNIe`qbLewS&HeS62o253E=K~kgX`_w0H0=bM$Fdd#^Y4z~8RX@; zb!5|(yWNYY2I@1ItQzXIR#=`^ihMCuz!W;fk1@;lG-eEpz(PJ!6n*TTEO}UbK4AeG zzAMeFNpMTnJ%8^Vcs`?H1m~pO2N=rnN6*JcNTs9*9eRa*tf60nxPL31=q)?#YGSFv zyv`BmxpvK9j7@D?#vj54I$dt3kA@vC;ICBCm#D!*vu?+0By)pl@2{pk2)EvmzHKx z;h4bvo4p@y{hj7rc1j*VETPTMviIfY!Sup$*aS^_KtrCra2^gkuLTd{ z&NN?L5KfPV#bMbtZobnWk#96?*D@K^dtq&T-Vw`t0P3fw3G(2Ysc+1<&{dm-^@H|5!3M7!8_Ltf2V@%R=wo}H7gO|0S+p# zXoLBRV9y;6&TC*fbm$8lA`F2FS1!y(Pd`B))=9Hia3JT-vry4JO-DB_Qco~IgHjD& z6aW}B+ve4vcRm4+*R=q9lR?7^8D+l^6@o^j-}L&n#t&uf7>?h53%_|}adLgzLd(UA zRS3a(RhMY=ov!IiG#LolJOgkv1cmm&&E1{HH?{mM;`rrd<8^j+?zi6S=JzjpCu?Z; z&Y{CKUYQr$jT!XL`wJ0gbUFwD+ugMKCnD}h?8&e+JY=RJVcBRc#`80Lc=3jxpUmga zpGQ1MdcrR%!neLeY>@(Hc08^gzqc~2)sY}RMZC49NQqS3p`dWvGyV^_Pt*h6vc$fd z)1XjiIGz=au7|B6RPc1qs|F;;7noK(shW4@dv++m`pV9x1u-$ZKsv!TXMw@C3=PiN z_&z&|Q{^b#G9P-ik{SLwdvCtgB$wG;8hPl?`RuKEh(9+u^t+TMDwvukzo>f9UEyenFycG z%geh5lt>Pyh`dc5@-pZ(2$TuD!eK(~Q^S<*?f?+a+u<2PAxkvY6jlPhih=ED?onjT zS64p@a13blMR47SeBUQ3DT#-m9HUbuGnl=rAohIxH|i73RyD1~J9c7ytbNJWFBjCz zYg(OZTA3bM`Y(ldK0}X*sdbuuRcsYbHArf@8|? zva!OXIkDUqFRmV<-e1z?JAcisL{p zu2qJ}atuE|e+F!6?kvSbBVfwh++1ZTpvGo6`~OHRnb_PS(bCdp0^Y)5{_`s`bQOdE z?4Sm?%w29)8K3iO3AF6Kn#*37$>NmmX=T zZN9}2wB6lbt?Vq~O4uBNNzb$wp~;R#dgaQ7{zp4Ys~P43G{6>Fc@meVaQZ+&XF4 z0s_ZXTi^4w^z?!!CvBm{bYX25Ai>=fiqQ5vfIkuD@6K9u^a(-Z3c*#BjhT;AiT)i#6GjQ2r-7zWO@_;Ye7o6z^@1HcKjD7(ouv<0b6Wp z)bzqa2*v3Jq^ai0VsM`D)iCe6EK{@9bk08jWaDD!AeW8R`0i3b^Zezcczu6rOar&Y zfW-5VE_ofOunZ{nr#rgaKAxTuFzc)rjSKaGN!jWzJ*y7SyZLetg$$7di}LD5pO_t= znBVn#IiE~|2#q8sMVaZXsOM*9uEI!A%xp_qo$Vjr zoJwsGD1H+N%j3JaTeog;vCuV6{b-ey+w_G0@eK*{4fmwWJXm{PS$P`pv~&gf0`Sjc zZXf{BBlYu7-7c0mhm$}7EpsGX{KI1*Pa;9ntym_2%btI|QF$#Nq!MTcPfu?tH9@h0+BQ-#;Z5=*&NP%kpQK+0SdPk}PD%$Z@es z-n+5|tZV*QBJq6+b>{^nYxix*D+96#df&f)C+sjYGl%(20>t;(r;i^wYTy;f00hVc z`-qto=tx$JsVuyckmZeo+Ff7=V8ldz0ReY8HMKXb8v9kXcXt;|Ngm4j`Ffwr9b(+R zy{Df6&R)Vd*Vh?)Tg7_>zsY)9cA|=kN(OLOZu%_NtY>ujE!;mC+T!5k6o>REXJC*$ z{)LQwsa+tkS+>XBP~BK9ZdxGC4)f>cuVu>x;m{zfFl4~VK(i1)Ry?EASRvGj-3PE* zTcsYP2_hpT@7(foUDwwpHbZQ%rg0d+Lrq_9^SuQeHA8XpTlCPQ&D%I&j08mhEAarTe2r!0&Apwl^^V*uZ=q3A0ERBqy?#^A z(dG7YEgKt>hYlV32x&*eTXC3C5QXYUmcF&L^ZBz;d3m{Y<)89QmVcklFrg;m1)z07 z)0Qw{m+Y=Tw+B>U)E{5J-bU4h9A*B+sD|5rGe`vSf5yc^?J_W65T~M^A@i@N4;Ddl zdwZo4-{nFq^hZ%8XPtL9Q}@tFCLDx^NMv!bOLC!s^>+CETRA9i0NE`Vm!xF;kBcP= z7Yk-jGZdZCyI{`v@eAVxZYoX$PB}7@of=sqAuAgRSh3?!ekKU=KPmdh32DlRFSYG_ zc443vhied6mj@y@(h`Q{;1tl-HUN(u==s%~_V8(8s4t)+8`wBn2g`G6p(TSuq}cWzPzalm2>aePF!muTSYjXo$Zy1<0S4(txu1=4zqmor0%G6p!0oN$5dA*5 zeWFdj9ua8thMnt9-V0p^Fk4gdfE literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_28_0.png b/_images/examples_Pulse_Building_Tutorial_28_0.png new file mode 100644 index 0000000000000000000000000000000000000000..4728250117ec1beb31865e804a816a7fe555b731 GIT binary patch literal 25320 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 F_%CoZ>cs#6 literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_30_0.png b/_images/examples_Pulse_Building_Tutorial_30_0.png new file mode 100644 index 0000000000000000000000000000000000000000..668f5b3000626dea2205638335abbab4bd682066 GIT binary patch literal 8030 zcmaJ`XINBA(mn_xN)Qk)14t4OQNkdgfC^qgNeYrAXF+lf3PUhqB3w}hl$>*pG7=OS zl$=4bk{NQ4B+OTXclX(ScE9}rM>&1EtE;Qtdf)2jw^S8qkFXv=5QJ7qQCEy!i< z?(Xa+Eh6IZ&k@2-u6IS=xazvXEQg&H_1qBTs2S;pEL}Fu20=I)l;r=^x)(h^&GNYvvGNv-pEL`jc8)guSO|Q2!qe?%|Egc} zT#%H{&$$c6_}yrN;m+mUkdP2MGBA&h`K}k~c}1KHMGnuC`@h4ez#B*a??X{|a6LDr zfrsM1zY_SkaG=7##|KAuLXxX&Ag5fig&IaB2DeVVt{+oZRSjWetPshS7DjPOI$t?> z@StY|tP>E`S9CWNAH>q>usBt2I_HVwGumPv$}^FT$`Xz1C3M&C&?$#qp9s~$U_LY- zPsl>aAvM8$%Tg|rO)STmm^R%Eyj%>?RTY>-lWIR!N$2?H?#Ncc`t00uZ>5f!2jqvf zDa~ofkPGHhQ}SLSK9;Y9NIUp%`#7;Vfm`Ugx0a4^aPW*^5S58jYmW0hrL?{B`i{38 z+cQxLWbjLO>+|x>drDHC5y#Xi#;8_HO1}0EJ9@D&jIC~UeP1`;n)>Pa{FdbjJU!yf zqa(|Y@nr6xrNT4((VO&ih)*8d%HQihQ2LpV7d>Eoa(@tUH~N{|m5XW*T^_HuG!^3> zKU?vxRNh@l;x<5pni(_={c( zU0&5YM(c|sCc+UaiggsXC(j57DAfkicJ`Op@fh>Nn&u`CW1NQj`!m~< z6zqP^ee+&xS8%Ezrp5VXetpevH~iuL_82EIy`e$*^77|nv%Pt7%F4_5>EUa_-)D{-IE zAZ@>P{rgi++i!)I6c%jm?(WlC4kaynQzK!?oy5-6a><7UHW-<>OZ6>hc4K|!g%2W# z0ZX`fW@e_Aq2Y=AoaAJAZEfvd-{usRh_1oGIsf9~V)=?2_2pgti9L9cIbm|MwqZ}b zsVLTYVaQVhmg7N$+1YVV9C_*cAxDV|7YLu8P{&lIe)wQm>Ak)pTUc0Fk))+y?S!-U zI!whAS2nc3h#>jlIVu7bwTahWYTdp)N!(hS>3lq<=<@!}y?SDw(LqP`os^ekX63bb zeKj%ezf@tnviVbk!2vx|)$WT^Nnpu-jQc?nctq=zX2T!!KvZwH%mHBv2&G@ep zLvPl_XQ^`TDK_72H5_RBQ=Af!j8az-c=6(emYyCSwZv%Nycuk3Ys+h7Q-51iGwC(InsP-UjX5C2-tJU$RN7d5C}C?VuJ?QG)Aema z9$~(0qz869f0`4`%6|?)C<-M|ivwk9Qc_Y4U%$TM4G#|&;OAF($t;59jgfN0bB6KF zEZ`P301J_YA)c+(>(7^C@;i?+b>v&{mho5W2er*0JTW`Vq9Dho=q> z{zU*>VaPsGCU|cy@&VXPDP|ii8i4*GPl*H|6c)NY3|*s*$1ye>>O%L5AJ!j3kTh;o zZN=z`IvQRCL8CsRoHIv{v7CZDrc*%U9jNq`KRzx{x9+7xt^hO%!Y;119YBytK-j7Z_3ZEY$GV)gFPOvEC`=~uE@<)FzErQe-Qdra^Okbqk|1x_F+h#@0B8-Ll6&-9E zl%KH63=6Wrf~QsQz%=scK+EEdb4I57J5yCaU*ANNA4U+G>*zp_CiFX@3zER<+*vz4#kPO7OZ}ix&#ZV$# z^dC_@2Ti>13Ovz63Bz>sK@8`MiQtY?JScLygT1DiY8M zi^>S5K>SehplYR4QO(ym0U!VV_#Ac8!M|Pm)`ZuFC3wqx{}VIzh~Z|u22}n1for1Y zsmFc~bR-uGf;!O0dEC2$Ifc zQ+ogOEofegzJs8Fom7mPHfbCwdYqli#Y+DBObsrAUSZ^!*7TkTBmLDGCJx0*nWvTV#ukU!ENW zFK|&}R=)CAOvU;LR$^hwFX{zPmMM_W0gVwNgwM~9VU45cC-q0UK8gi3F-JWSQ8|Fz z0O0VfogxqjLYz^iK}%+EY!9Qr5&$%#m4T7H+uy!@Q@(j~er?Nbszp!cJUFd=_-rXU z7D32q*v{XeW)|)`>c3g58kd)mp>Av(n_^LfN0}Y#U}jbi1~*5ZmX$T>?(XjO9iDLY z=#V1xpe<-dt4a3DR|7cCcBH8E`uaisu54kjAva-rw_EY_LJ#{(M5AGiJ9qBL&-DqW z1bBQ@dZDsvWmXP(;A|j_wnT^Dc77n zEC+Jj8zoCgO|_kFOJJ43ly`J=h~N1_-yCyM$7f?9SU%_3xr7Ukt&Vi?20^Mls9*{* z@tK#PWtY~n>&aGkaVZ$~S-jlc)ipgr+$#fgFTV4Yv81fb?#Jin-qQ(B-NA0f?qF;B z!Z)8{&zUl)n1FR{DJe`ad3Q_9MG5oI&s5_?PM>a@`&LxqI-#7SS9BfcIj>=5Wu<%M zAiWu8hYlHaHadd$-|mcaw5d)`=53C;(5mq#TB%Ja;q3$QBM{Ngu0@@9(9qO;ZPgUn zKGExWKX}<9^m1yBL0Ly8b<7PkV=*Lkr60^-+mo$_H6EBS9qj8%Cl$w~(U1Bc?iGY^ z$~w*jlgTrydXgapqfp&|rR)zh)YHoVFt%%nmeMbCj5#MJCSmr8BFCh9FVKtoPnyKg zf^R4hNHE=5i%a0R%9Vz*-V@NKo3M$3+*>--{8mhXG+U|T5G(eF=^y?6Z&Ny#%^OgN>9fu>HNs3<9G$^wtY8yr z5%!R!lh=a}X7gD7am|;wGt%3WW3WE@=qO_lQhP9u?GVDHAO7Zz^+=U3DGNV&HVGdZ zE3l}yg)rH!6Y)!!=}gP=S{ybzOKFu*88IXI>L3z1k>|ggMU3NDi_L~_R7*-qSS4)V zc&|+}dzsxDl)y>Vxh6r}q)dY|k2_FeG4KDa5%_iL9fgtMk z4Q~Xv2mJO{qvv}DAFGq@66x(*6nIFP!RP2aLNX+Dv4R?Hb#j_q!{c8dn zf-|o>LW=jVU%#Fa6zqz{tCo@0)*74)Vf$6os;!$`L8#_E>2l4GO{)K_qn(a`k;QTmIBv)@g@H}kro4L4! z4!{Wy5Dh{mRfcchzO}WsZUZ{Vrv$Fh@$e{t)N~YJQt7bEt4w|GHbu&VhK7cYhVZi< z_3U2O>$3nr!Hp9d*RKZ>zkA|vD|TFleTArRo?}Ve|mi^oy-u`5PD-M!$IZ68N6SP;bmd zFIBlfnpdq;nvRaS#=h&?Fim2ouZU3vlXKQA7Kuyg_73_iR_a%Jmz;O#Plh=D zI<-2px3k!rp_wK#6J>=h>cnx^mima`j9q#>)zT@8A zF{bv?+aN0wW9r8a+cf|5IDGhUW?o*IyS>397l{1hoSY(|^~dsu#+Fa>@Qh(Y;GB+5 zjE_(E6;pZf(UFnq zA3j`ykl8NV!pOigGd+D88}fbKdwsc{(_oJ|V4x;qu?a7fK!#9yZ+?HezS1Z{_)+(g zyVa=N`A-$BU^`FfCG(j+Y!!xC6;Ae@lw0b`>Zos#4NIbsL1}tHfqH!iyQYG|Q-J$!SZzSsQH#RjGi85C9S|L)hO5$NqX%%~ljo(e z{t_~(qyJ=|gED>YF@ucnWbg=bh(y(TQ1u+H@Ck6r08JMT@_ni#NF8gKyc?k>%_))! z^rht*kjKF-;mLf*6UgWtpu8En$LRJ)I?6$m9@f7;&KSrG7=9SLv)(yEk3c=@%CGSP?)L3H5%v}9+_=VO81%92LP;6(#gVdNvy$SH?P zDtzi;4pDF+5*=hpWA{a^;DMeWg8Tx0$9WFBrF)SMaYlprM`&*q+ld_|d?G{215IgE ztO>xs%)!p@wR1_!lHXy!u$Lb9{x=Uz?W0FUfl4DMU`7~toK2S*sI3I_6a1o=Ksb;V zlE#gi$2?`aPuUKJakPn+m||174FH_g?aRJ|4Os;5e9tB%BgQ9PFv~KJg|U zL7d@Gv%iP=*qaLcZOB1~YG64t5N3~_#I^#7M4U+iB_T&An@ZoJsr&%R!l0%7@#qua zEOZfy?P?X~t{T9tJUMBok%2u`?9^RDkTciO*CX>GDUirua95;m5G^TSYC^#~qc|_C zM4~u?^n!X&Uurr@28^6EOl8`OEefTxZ$1NuVc3OZW1tv0VJV86f{c0Q6k>lT1>^P* zo>FH?D8V6<9O}vy^!0maOZ!uJ5PY!N?86n~9G6z9gow$VT|ljuJSW8 zGu%6i!<7b|sW&z6-+#|>f1ClRr?F=0jrP6W9SPTQMK-@j1Un~OHeFut!nu>520W#l z$%km0n8fXkopAla#`$S~AXl_j?)%zSV)_=+D%!$#SkVIpu z75QakR|?);O5@q)@Fcb5R=x`Gr4D8&5f_PPWMqaH!rBhTjy>&mUMUa3xGIOaFD^X* zGVp?02?;puvR-5yR&{YoVfIxdYI$R><-<>pQojSnzsbWHVd0-nFJHdY1epQ+McVf! zP>7nnFf9!Y*P&e?49Sy|R>Y)~6h2HfVk$|hxz52kAItYyfsSL@MN{0s{B0v6qqMw? ztSmyu-o(VjY@d_GYLwq$4Kfk#+FAUl-onf{22D6ZUGH@GWQfX<= zLYTR#x$6s!&h2kTl!A&k32CvuLl;IE0NV4{8@+sYeriM8_7aC$jb~_mQczHks>$nD zui8^~Ux$UgSu6S0G&wmrkiFewdB;k?qHoF3NA}{qN_J_^jaNgKHHpw@rA5W&h?(3= z38C{H+dENLU;k_C91>6c!8opECLsB299u!tzt(wM;ICo1f=jvC*;=G}mM3uLjE1FU z${&Kdqa&pb1LuJ=ddSro9b;x5ATBFvJT)&O1a^LY|AV(TocO(No45v|wUaxK=Ugz; z!TEk5fZ}eGHzua0vf|^fz_+#z4&B*$MZ8MDcG?2S$hC}(g_XP~-`-uTAwOz8(~fN^O@!gBgLz|fJ_13?T_L$(^R#6eJlSEG0v{2khZ*g*N)JWvnSFQbLsxM zc;Bwwjgh={LN6gD!not5NL6}M(+y~8`l$ETlG?jg1(#7O8a6gF9ucZ@P@?Y)jD^bP z)^qw_`lJoL;2Nb(EI&e9)*$$Xe)^H_140~q%1*Zp59g8}qWd!M zc?gpsqM@&^|0X;d8#O7-lA9NB6aZEJ@n-?kO z>PEP3d}Q4(5ls-g2=f12LnsWsIDS^#efkz3KYtb|1Gv~I!130If~JtLYC?gfngA5< z&RM<)IU!~4r59kaJF8P2);Q7e@F4Y^@WE-OkCG41I}WB77M>H*E0o{aSnQo{Ptw-V zNCXMkZ^SxE#fe#ZFxeWH=MUPbztkbBzrSDH<%c{_K+ptSU@y+Onvr)0# z%lJ>9t_HJ+U5D05g!@F*$RkSH4o$VoDa^+AK2FXeQyF=AI^NzDGMl61!E6^=7F>~f zNw0-y&}aMoOwd*-g+)bxb!J|ASE#Ilc*a*I8Z&cqZ$q;Gi(;^}uncemWvdjny*6Xv z#urf36enu}?NYkU8x)D^0G^*S0|P1VR~o+`S_qhTEHij|l(RJBOwD`fK0{u_JNXg} zSfyN(hbEAa0<&7m`4PAX*jOmRF^#6e>6|dz`?V!~Zz*2p*ODAgh3v#wQL9X+SnH)E z=$Z;hNqt*z4Tw64Qc(O4;$EAYiQENd**)1DRpK_K3UJs)+GTofZVyQP@Te%l$mF^= z=@m(nMF<;d09XVcKkz`;;sp2e=g&!!5R%tF3ZxJ`p=oTNP2&u4Nk`(#LtZ1@Kj#NL zHio5sY%irdl`r{zY+-tH8eErbLpP>UiSG(qu1#_72`Dv zLee}(HjxnmCRH^ib9>uc$oPY{|I#Q0QIWPAJi#nX#`o~Rx9m>owfBX2qH-D?#oZeuF#CO zcp;{LK^T`57f*vsub8Q7mD$N=Wn)|486j4&ogqh7;rghCeyTN22XxX}sQ)iHi~5kt znZ2iU>>AL0lOg_cTZb+K+ZhU^h(s`G3M?D{faKhgbFr{UBK1;=i^WrtlTV9@X+n!@ zc~RE$+|b!=$+VIRj&wY1ka5k58zgfmP~-sz2h%)eV-Xw;GtO`zzy)_KHbJm%w7f= zSz^<9iv6PJ4RCD-B!L8G)0wzO@;vk#PN_y)>wHB_nSdRk%)8RnNxhB%r)nW3{)QNr7yer@n}VcG|)8ALN#0f2|XaBJ+{|uXj#RlD|RHr#i!d9IJ=8&Ix7lD w!$|BC3D5;%{2!OQzZv#_z4!gMS7Cdy-IMR=)rIxLU=l>>nyP&6Rnv$62g_EYzyJUM literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_30_1.png b/_images/examples_Pulse_Building_Tutorial_30_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0ac4aff308380330461e3ab1e1a2fc6e14009519 GIT binary patch literal 9434 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 literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_31_0.png b/_images/examples_Pulse_Building_Tutorial_31_0.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c7470ce7b9ef6fd0bab2be8df382878604b1de GIT binary patch literal 9723 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 SD;$-AKon$^9~R3P1^y4JJH=T5 literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_32_1.png b/_images/examples_Pulse_Building_Tutorial_32_1.png new file mode 100644 index 0000000000000000000000000000000000000000..ce81c66195f840c758a31f1305b8b3fbe1bdce73 GIT binary patch literal 17891 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 literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_5_1.png b/_images/examples_Pulse_Building_Tutorial_5_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8217e92f9e0535434d35dd9cb84fe96c200e7cd4 GIT binary patch literal 20745 zcmb5W1yohhw>OHSbcl49)S+9tK^hK?ba$74AWEk+9J-Fu(jAi0T~g95Ar0cU_}_cK zG2VOMdtk>Ce)up)JC zc(-^oXXt#3)zR_=#mbOS|7erDs_LrN#htlVpT_Z``-O)$`QN{9n43!D-We`yT}_+b9PoL9p%f7w9{z)dDD5TqXFMGT2i@ZpiY6us(%_TQ*_i}> z9?lXJ#PoQ*-cv-4$Dda}i(A0d#a5o;;NbZ1=G9Xf^d9wsITj*gC2{GT3gq&+@27dAUPYiVqL(8=>M0wL{=GDfz5z9CdWUQ3HCaQhj8 z$-fvwmzHJvh96{0*zS##Wg)BmWWSN^*RDAGqyKVKC#Z&d>zbjn(s zuD>%EMsn?Wl)F8QDG^NW9ZY(gZB%;ya)jM|*S~&0ycY0{Ab`@H;5cmfoS?C^blCCp zhoC(Yn}xCk<;P$9R8ivoFKgtfrT@pZfL{UMNWjJaHH^&OJBc}-#J9BOCe27rm2-#m z_=qtQEFohk=?rXn6=?C1UzHx$ z+%n?-9==HM$M0L9{&)Pp{Qn*Q8i-Tu@AniN*Fs5OL`bU{8^<-YZn3`h{+*ew{J2AC z64OZ1L~k||uYVznga$P#QuZDbgjvd++^g|35yR`mKxFj|KPy& zaGtYuo41m)SZmW`L5T#BJ-g?yXLkunM#9rwh=k86Y~9Q&irg{*6~j<4$^jA)N<#Sa ziY$2Oh_6L230Zl>?AH={8Zo)iy!OKQ$3E7U>#Udfo#TquoIl-B5nPy(#1BUQqG)Mn zES03DlcYt$;UHP^^70Dgs1`xr6hYDBzI^$jILYRQrK~k^*Ol`*lJ)jzSCIbJFSir| zt3TqbbWLiQSW*08ba0cMMI_^J56Sn8p>K$sRhZ*B&kCS89pLP~EM5z+u_GvN!AhIv zqOtw~*HD{c?^jQ^cuJ8o)5G4PI6zM3zo%#{X=%CD)d8}> z%Z7wQr`oFoPDVouw_P-|4IRZL8b~m>ZTH4N9y5Ji@T|GnHyt#EosnoPbLe$;gpFfD z*TvY@WhW_0`ZR2b1vf3=5tBM=jsk8Mi%p5xVF7nE@3mARao1AZMxQj#3&IK)0XL{ zQ2#l49vj%&nUov| z{(gMQCWF{_=fJzS4oi)$Tl_d%AmJ!s5&O+Elj^H=*tX#HC}EtZRNL07tVa+aFjOs! zH@Ymz*paGrxC%*Xkzy>q^MARey3dLEw(`^1bj=mA*tLZJRm*DcRr{5QGz|_0CZ?&4 z4N8P`*RWMQ;)qJD#0Zg}xt+dXu0ck8hc1Zb1*PVskpj;j_-P4kuiy;v@9jop1*3pwM(C*5DWyh(j8 zf>W*}tOJRBYRusPwNJ6KYhqO4%7c#cVse}JEnH6gOjSf}TB6X@oFmz%S1~4+)Y_Q5 z8MVNKmC)eFr7vUXf9dU~K#(R#zJ=mFpZJN?{o-MGhI+c*5u$e&o+*x@)E4vtFSU+d z^a(-wy|kuMM7q2!RHm^0`-yJZJK`LYokL^NHzT76t>5eFG==Fg*=0jEG=z;3t86U@ zXUtyZCr1Pov)hz&ssABLOV}7uAv&Nzg5;mvB8EDymFSzO4`3KheNKb1$o#P(PpCPI z_ODJ|7;x=2j}xQXY75DqacPP{^siBz7NW~vD(vKW!Bb2(WNxAm%V5;mHA7lB*VtzC zj5fLJPM2?RKh4-K&`jd5OR~Q|ge|q!BHyQ5URqJ3hS>IFUaAOOy6B4)_nToQ;`)d! z>y)3P=s6dh9CiGLQ-u^$20=^+!KLGb=H5W{4;ltgYQDAkwqB_ArlXIzRbdbLfM21rDY-mTpaPVN!;IbW;TXDMcUx zTQ7_{m-nrVCY719EfutB(=GY?o8&5vmI}pVhJ!b~=Ap^0KLTD(C5HC5n-Edt@l1EQ z=W#3=CPjouPrrE^}Ps*qW+yQ?~oSh)2^>plLo1aAGdwG7tHOf&78li zyU7>xq+KH;<^I+2BJI5DluB-4^e zD^Bs?7yAUvN!Ea|%PBByK-Zi&e;GvjjGuK`i$oAH6fLB~t%|mAblz_;UZ#zXiW>`8 zeF`TiG~oFTkENe4{EFz*EwSstYs`*Pj`YS<^TZ;7hPY^ZSUKI<=`rL5wL@}|YW?B!T z5~NUTzdb?v2&H>o&3Du1>S@dOr-wBDm7*-pR?>^JQ$*(Jv-+oC{@&cEl%+8kF;2N+ z;p@v(a7kIPpT%t4#;S6T)6s~JSbZjljmgE7O?}+DW!7fB!X2NF`_OYd-(1)_a`4Eo zPk9P;tE&+P{&)FCE?D!v(`3<8(PilCsYZVH|xiTTN^+RqWXi zGs~c}mq-NNZ?%Hs2vUE@jYnom*}&7t;;lny zt)2EQ)}h*MaC8r8zrQRGf{NNJ{n*#EVlQIntwMzTY7=adxrz&U1n7^^DZ|cKdn<}m zcP;H=<%1tg0%%FrU9(h%75YWho2uR0tif6`uDi4>9)vKAX$AG<`ST`*!Ui^%K@8fC zSZ5@O=B3KN%Z+^M%#n82%lsMY;X%GvXaI@*O12&j9N8l41ba*E15ydN6`eits{lu7Mi zX6PosxB{WD5%G~i%*7t`IsKcb>mhL>Tvp+#sw5Q1%#c*}_xT6CjxWm7D-NDBZ(W|< zfl|*3wm2R;MEX~*+IC=faP|=Y2}ujty+AIcHs7vF)`n1gf?HuO z&YRnbO>#U4%5fuf0#dJ51b&}fXy7ge#ID-MH(F{SUmrmI~QZs!LOP}(z zQP26|N;oB&23BCj^#(m^_*;L{4{0;3j5JCZJD80+ScIxq$%IZtPxf#Zc@EhG)l;gG zJ{<$LXI(h%BsExI95_76$yQsH-hIc=oNluEN%b1yLaU=yMDep2^EFwX=p5Py@_e^= z`-N@Gp?$rizxql^Qk)7htp2esPeuw~A2ew8;FhnueK$7kp!neS#fnRfo4C8)>>|x7 z0kxO~UtPeZtB2tOSmME5mCV6I8Q%c957m8dY)pGVJ`b8bDUl7nAEPt%f8ZRRqI-E@Wl zJZQ(+dd3LhKjUbBKbHJBgOOU@9&3N*X*Jx|BW5@L5Y{qeDtt+!B@wTnxqH0%8}RzV z%^4^!6V+ri zx+Y2^Ff{Y+I+-73_~{0>C8^#^GJX49jz@PgW>DA6%B~`8o1}M3lIkUImCQl+<3liB z;-{AWvM=5lG@T;MLize_S}C^cHd$+%Cv;Y3M7>!)v#i#LWlwgCh10S%qQOs31PN{d z_R>D<2?sURRp&koi8qn$dNof#Aa%C%n=`h!0Pd90k`cRsld8l&B*kWR0o5W z?VBJ_a2IjIjw01*x{og8JqerWy-$0-Y?}HDT_GfK@AXX>U!yv(rM^W|Ax?t|jPoit z$79PrmURU*r6>;fx9WJs3@L=|>k&~mMSRCk7_HYKO1&jzR4QN1wWV_GgIC+J;(y>nm$`5d_`q@j-4_KJiXudSDtq>uQgo@}GjWoCO)6URo;T zFrEps3b~Uqwv)9iMCDSwB+nn*+wIlXh7i>eQ)O{H48AtNkkg}~DiV^NP0_RAIz2P9 z))|D@xChS|pPkj-^?JEgSgg0pHp0PKJl@-qdTX%#4L?lxw&ypu(oj0w{s%n4g;h{g zkvE^ycv4wCDgR!CrlttD2I`sid4xgqiTxp`@~fBEU4-o)>g)MhPh*^W*aL4Al~h!^ zj=VP3d!w!aIg5%R#mdegE9ppbx`-pL4n1k<{okQs-B^9L4t${#A@h z9SWm^y}j>`JgW7`(Yagf@08fu`(r=j?<9(^2CXJL^Rb93#dK>a5)25Z9UxobxItQl z8#QVnCVi+P2wRX0719bWwzvixTjD=-%LF?NUlLD0ve!#k;G6x0GGJ^V+G8jM{yi`@ z-xJJ#i{m3`%qPgfp_^`MUGPDs=ZzGeg1o_@s z`=RF^>P)3OeVi1jID(=WPjK3ziT519^>mrUm%aDyGz{^R*<^0G9q_#SEFtgp>0*si z-}1SG-tCc$KX!N%c9v6pdCg{9m8=&xnI`JGs8s<>VW*C)%os(4zD&^g>l=_rRFajy zd{!AIsTZK53>>LByEkh--c$}i)VI|I=(zSMGSb9}{bFaBO8hbLCzMLZ@F`t6W z>tRD3!TkqN5f!?W2}h>^~CAe48J?=vjPRZgDZH_sTnlY~TH2v)ULN z8M8R02}F&m{v^Woj{}7X(A?uBkav>hQ|tRROL&J#<=hx==-U{yzJHexKIuf<{r$Vo-uO=6VX+?5_-0UtgNH{m`Y{yIYR;nSN`||36idZ8Iargf zHyhWghBiEvu*I7u0eYdK%rT2|NGYv}ml{za9!t#|BuUhV`n(I9TXP}uUiKu+_CJ%) z6bqyX{C5=?hfL}J;^^q;y0qiARI)yvk%2cOsNx{hvGy zcaach!_l8K7z7)j!q4DyFgJnBS6iZs-VCU&YvkPG(Zr(!Jp9QaTsMzMp;PgP8dT(` zhQ`~a!!@8TN;mO-2(P1^t()w^3EoRfUHcnq75z&4@w+F(=f84ZF>gr2@6Pk9j(;L6 ze}8)yR8`?zt@yD&Tsr?F2)y5*M4#Zxw~Q#V5q=P#@SE=;(=eQR{XHK6pt8JS>|bS| zWW7QPW@|wHO;%TlKCtlzu}k1XKZ8I_Erx9^30vx7;Vhn!kzxWC2F9-mIm+IGIMK)` zLiY9KN`Ja`uqS`T3jG#;O~&Ry8sU}@iW;>92$115UIS_K?OCL1x@JNh-< zlQ3HPx==KU!GCPuIrAG09_h2Z$DRdQ5ZcZ;J7aU7gQd z%Sy;q>yF5L`+GmmpR%;LC2w_^YPi1peaPrxFiZhF~P zMB-#F(LvrGeN{fF-GzfGB{XlYfR$kHF(7|aB%Un*NBFEnf7*$N{VReYyYt5n6dq-6 z;eSi+W92Qb(nC1be^aXaJve8%3@JH{2e(WEigDkRRwq*-lbOW7cjNoAllt@kMQQbJ zhWV zEaUyw8_ajeaIw3Ev6Cp{`=^z@Go@$?6!}^Mk!@6w&~p;_A6<)%Z)wK+!t2rgxwQiD z(0U85@o5%=->vN4(kY*XI%+4u@~f_RD!tX$e-Yu*7&A=0?dkhBD4`rTvnIth7M-&^ z8jL;($CnAefs~@M@>QUaPK7|&p8?b!Q-21)-og8K+|E?4Z&3bw?d$9NpXcMS`>%+9 zwFq2Hq(#|&_Vqrz`+At&6N&Q$i`D}_LZg9`?RhY6?hjNe_d*c?^%Zl{IpaW)gn?Il zjz5Fd7@t*s%kRIwzJ7W9N?(kLgLNX>A3?362$Ka%mb>_su36xb6+=HpT)Oc-h8dM^ z$^CIi2O$cHEGJkQuhfyk_?KsjS+L6_Z#jl?yC&OiJ<5WRBe2GBcQRqFA<$waT63KGhdH(O=_}|I- zlxjg~Df6+|!`uhQ#qK1kZ^J@Vb*Axm>} z^U1GI*cAuvU+*_z_nHbAN^`2h6TbIPK!SbTVDh6+*gfNzq7D;l-V`WewMUG#^q&tI zKX5&!+g~6MXfA&*GjFBI7BI8NdoDUQtko|%4K<)@!mHDHczEsSwQuwM52m@4hm|De zcz0n~^EMyp!%nm0=Pt+u;6-h&cz#MF&92j$_=(+r$E%57!TIyMzJGs)uPTU>8qiIR zoWuSHOUb$ILK~s;!1$kUx0m5u9wVBjmZ9;G8<3yh6d9@5ptJ=ux%Z;TfmrhKyr%nj_}6IuCOIKJ~r|pBkhP( z;!;1OqN0qLPntd$q0qP^JwEO`DI&yN#A`5UFP?Wx{xS$pICS85yDVvaZtb^a2E*jjioKrKP~5bltd< z?MFbNs;XL0US4jJqdHq;PlCjr**ERdac-a`wdY6WhN%olMI*BUVcPb7(MVxJJ&IEA z3N-LF^z2s%n%tacbgRdxO99VVG=*g9B4WcFI&Y%he2NOa0!f;k9YM)A7G{L#OCw48 zWa+8d<=CuN$&iABW=&l&14DbLy@I*2TrVd7(g#pW{`y=lSf~1rOi$xcyxYV%T&O`7 zdpM1oTkh<=4-m_r__{_ZkOLdZJwC6>l&~$Vz#;v~pWLb(IYjpyS~O}tbaADyVBSK( zX=)SMR&-fp3%NBGj^}2?)S1g~clX;Peg20*+%iG&UmMW-&$C34cr*0Qc%>0OM=O&$ zeM8vEVV|2e_uU>Q_CJj>?g3CNTAb>`CuHpWrY7PbaRgjk+}G~g7%DlUF$WbbSXZlo zVk_5u`~;=Tn)F=7uWnVyrR===u7mS)o14h-=~o0IWm*-ptYI}?C76S#_I3mV=}g^x zAYp3oCH5tAkI{1SQj6#;j7Qrz(&D8Gh$$FT*)YB_IwS4JxBD`x5}mixqh5O?pnEo8 zYC#D|FuUg3_39AJSBc+Y{zK*aBYPq;vL6198)b*(X8eMJf<~9MrwG5ukA0A1$%R6{ z50DSd{2`p7|Q`yeM)gWZUuXMY$tSv88(ts+#s|QTzti zGp+rf0&GkxKDdUoGwiINGOk{zYZP!DR8{rh@up@F`c_VfE;<7Z=iBoPFamtbQuTgOQe#!AGAeE?VDpvaZE`opr# zA?#0IMr-ER9B@3wfgoqgNoEQN)M-v0$K_9hq1y@diDk^9yiToH5Cy$xHdh5Iy+NB* zMIxWmLcAoT0+4qCiP}wm6s8OeS6J%r=M6}{VOS4E=lXR|3|HY2@OTc!ocIxJx{srO z`SnZc%_@LbP>GjVdz&2iduax$Hxgs_n&6C>8~aE=*u?E68eN*;=!0FC^Dsv z+}}M#04t;0g@xutS-V`Ez1wSg!8 zC|0R*EOdaRM_!*lYmTFopvU2N@b+u7S@lEA_uQTpH(+hD(DaYcAxObJQ~VUK(AYLh z`V@|DhjQGGHKTl~i}te&HF7|w^33k8AWN3cv=UMqyKvNA>Go`B(PUZ{St62}e+wI+xUxy(4s`Y0Pnx7BMZo$y zlN2Q9JTW@XX+qhv zX0ZaX-Y@}5;l`}Ys?mZ!m%4virIcj3+w9ar(p5?~63nj7I_2-CBm*KE*xR`fIniUE zK>ph?3Tpu-WQCDI%ltv64m_ZS%fPL;cKz0f;FVYkV>6ykGE?;;DQx^T2?lkbEGI|~ z!8Ilag|y*T+~JCr`GCbehm|}|rK66NPo1!l@qm5$K8!xbW>biy5QOH>W>QZ92-R>j zR>4(>bv(Y?#lVVHUnpi=Tad{Dm?P`e@DD8P09V}g;4m?&8?f;>u15X59HFl(b8(yD zV)b%{FNdC5GxTjrKkT1@d4dSdy)cS`s;UjwUH9sToypWWW{kJK&+)*`Nz0ZVJEeWY zprC;n92u7=4Wy~~SLRc@q7N98l7B@~7eCD|&*hcm^%kb!XX_5t&Wmt)Wn90yL&7!*V#)LS{V3@R+`(<-zrrMQ1yk_9Wi_9AE>N-y^OhsmYGmz5AZ5Q%xQ z&C@51p;Y|K47b32%PJUOQCL(=uo+8$E&lDWIdec<@%yKMn7$z8ALWQiT8o%f0A`|q zJxYtPQSEWt3LkHQ)64hg!FzdrtBak_KqURw8mRk zfl4y&bvrajV>ty-YmR&)>; z1tO3ehHV-g+2a$)vgLq?%9xmDv0{iB`ls!{*l`a9;wSzG06t#_q7{e`7VYGFD%&jj zjZY}5rUZN)7Zrh4y7{1V?%dSAAZ0Dp5PQm=-7gt&n4IX zpbM(nYlgW(x`RmNXoW=SA=hP4T*F^xmA&q4X>Q$7NRN=?G^%4m9szy>g;R!sg{Fq;O-2Ix5 z(Yw-42nJWsmWnZ^9EU-Zp;F*E7ofsd`{a~fR8!BWRD^Z=kozUR7GIX+SVV!qr^!&( z7SO=ZvhfK@)gOZ0doZKPP0VCHRnikdw51z2rle9y-KtMfi5=;Zs9Uz?Z#Wl~Z4Mi{ zzd~K}C(R*~8jGNrkrf{BzVre_4K6Ez8z(%oWI>uyT19g7fe&8{$Q21s6}~2b)4v#wxKs5xvJ@HcaK>=Vq#o#n(@%J-h}yZ9SE{e>D*nMnEmHAGepzYyF|l zBpbcv3iI7bJwQuTd@IbPL;%S7wd=-Rx`x9hzCdGmeaMlN<4)9pF(ss6TYL2qX{?QE znI{xtKfbWF^q7pXw+MG$zjP*R`>v+`Flq<<>DnWaw3@3UyQ}9I*F)y+(2Km4+(mP61wnVb}3%W%$x3OBTC5$yF~pB zr84Tw9J4Qrn`j>8DT}1Vj^h38QUYUI%M6>%4VuDDp{E6{5y+q?^;y+v>8`*#O&HxS z=Cg?jPn5M1Ot33%d!p3x*!{H`Woi=ka5f-P6U}|qx`tmh*EMhjbk1D;-rKBtBl>+w zrB?m`Ig#!%Se36zp)-mzsYT1Wb58PguX6z4<(_Fr!Bo=+P?$3g(LaihW09dp@}2O$ z71)(@DikA=kef4QV`zG6-073gejPIQ@y0%i&f5AjEQ`~@JXiCzJ8y7KvUYfSCG~Lj z-d04AZgVI1pCVE@bnJoKkJKK&JJd^r%I!&R$Fe(hS1sL1v}JoTuA3IFx8AzBaw5T> zArLkBQ%DhxAvAN!aT>He0a^an#gIUfh2|l8PO`$pCYK18iO=CF@@vu#aa}2&zlEgz zlGs}e<)9zq%m<7yqb2qI=9vJ7p)jH$Kfm>IF^^XAnIbc38V!G3kZn5Hy?@yspRG9Q zo{Y(Av-tj7q2m5R`s-SwUHu4ciorQgat%pram)AI48O{7?NJD0=RfJ~!aB`z?d$F* zAMv%m*Kaw`*j8XV8=dEf7@h3XJR6S2Bqi)p!;?OM4cmY2<=|j7x8wV?0&pDlbA+#s ztI^v>e(98rNM@BZ??^xl!g|#Q*6Wo#a`3CJ$G4oTDOgs zSuSXuQ41+bp6gbTx`|?nd~8vCY-2lL0$Po)u@8Zs>FAU4IG+(v$bVsO(?kmnY63+9 zRE2QWSmGQtJRZlzPtBv{Ah!E@(Bms?pO<3WTtIm=y8BH117(r~1`aw&xaiNH`iU4w-fGk1$a%-EzA9fnpP}wHzyf+;Vf{)EfgU#KY~~i?sa;TT@OgSl z?j}C!kPyPb8xX5ChzOL4VTBtgD}7yGJ3Z1}%6|}*?Dhe{n+nv(5R;h=cM)OHe@mF^ zvFf#JpjGvLGKz(vPBu|W2O~~IRLuFcyNSuZ3t-GAUA6{C?NlO!#rD#7D+g;hDPIci z@k@WSYxyaC30{Gw_7COX+Vf1lehr0$*|m22w8;p#7>+Gio0;Am0Y#)> zw|szCWlr1Kz#uce6%Gz{o@2gM997@HZx4<1^i=}HUh9z`Fy%FR$jZ_5Bf!+^@1O|R zw(&J65u4vQIm*kN%}@)pU@f-7bXq(J+jc1Yj>{ua+CdTgu?w0nVI>uJ{LJJzSR_Hv zM zX1?{4ge^`u;Vae*J#S2LWsRZ%Rc#_jW)@i@{X|uM_x{%fj9V4TA>yR!P?yuq<~(NU z0Y>JP*97{GcUC-G*M{_P`fd6{d>;v%F9XR@$33g^)F;C`;(I+T&2eJ1bb8Eeg!*1b za>|Nd1TdKqjRM65T`Rp`izWV0hYUVt;NdieyeQs$)~uCQNi8h&ousR1+s*G60sVG4 zf9{|bWDt!%wpVRt{3Do?%?elK?S6;b!Gi?H!p+GRzdEHA z7({)o`R^HVn zFj(`FN(X6J5I= zh9`iol~%v|axecB*wB{jP%N9zUy108HVYpoyGF_dkC#9iN!GKJb7 zeo~K8E>6erHNMsu&8@9)X9iv7rfPOCPn~piw3sSg>T1c3d28h8Ik`i6&I?UR{S+<# ziz&M!74foW0nsK$qFCPl`{$&Lwrt@kx8IzGWdR#}JWk83M~gR0`%2k6;zAj_{3|_v zh!Wcgl}~92NkI?fuBhv?h+@=kFW{6;UABRG29ONO4-tFOWNV7I75DtyVF3(C(e@U8 z_fMo>w@iQF4K5$qavM@a-Vw;mk*CWm*6Xf!I{*)BF2)30;1kV~M^S}V!5g$0#!Qw( z1v4v$`ETXTO5s6eduCW~Zo`u1^mLy2Ds{1ugIDow z8y4@%s^gDJttBT--fv_}b|;Z$w2xp))KwbhvwnK>2&V97>gemDr9(jK2OAFEjw$Bq z>IZ}VD4XFoGfdNzscH<)F`m`*n{~`DK2rJ>ALA7~yvS2VKmFul&K8J(4Vbu%>(Z2z zh67`;Af zbc4c5%nZI7yexNQIau1LGc&O>b@xtT-)yakp5@PT9P_l)``C@ugPsFZkr=T^@$qzm z!4*nH(_Po5H1Rk7_Q|V0;?l|vuvV8_*l5YzJgk*Pp9GRRxKbC}ECMe6OY$^=iCbA~ zbpKTg@o2$x1Zglv1oSC0g65{xy&VqTC3ca9JjW>mOldY-E4_BIp_lNN_8BJeDm_I; zZdI~&OdxY9NKKReB#cG*+BWLSK54M?v80`|%c1KW)Yn`!}0 zk@+l|g)e&hDo0son4h_O71~1jDh&7>&~@gxW}IS(()7m-n$oQ~g*ff}CDq3pEeZGe zoy-SNqclK<4xx~7)eWFCbB^YJ!Se1~L9t%+1+i)^KrAT1v~-SP$=^c1ahG{pwO{-8 zvZm(I6u?qcY7c+1lT(7mBv54%y*;0mrjhNZ(u-9$cpUj$)}~E6IQ&My*N##HKCNx)Q4mbXd9T@ z;y&m&Nzdp2vK$d^XbEi{CW3#+qDE9&yI?pFGjlCt1Qb_HP#$@Pg45@V0TDRouH@*hTaP&3 z3pU5>N!`GPb3c~373k~>FNzKqf2)_LyvkiThE7wYOw<#_#wbjs2&bhjv$06k0`XQDXMc5 z@70-MJE+*B_@{!VY!0?pr*iuwLtG6>yun!mlqH{MJ&heJiz{rQb}GfrWLcdXe+tZ3 z^D+$iE2M zJ7%`i>8(S^%u`DXOGRTma`;ymnIh{{Yh?zHT7J66^9GX-Zg~wc*Ot3|G=p$5d%h~$ z?~8v`VY#iUbbgil@6gqzOF1tY9E2+NZUB^DPGBx5s8}92K0x+Odx3WPR2-ag0$haO zQP~4RRyF5QC&C3hH8D-~>hzVtXQy0T24wnZ!SwA*~v8vE-?wdjq zocP@BqogR+xTfTIspvqB%5&P_+iQB1!rM<+swLwJwW1C9K+iH8Aqk%&f0fK-f9Lcw zaLVdY;09OiHQg#NPJlgN?JX$1LMZB*<`S{CF8NOd9D@9hscS&tthbe7XXjNBdNPi` zCjgc<#h-yOl9ptcoi!RzZa~~sn^9GjR7aJCmCD}Z1e-;w#wc2DfP$_!=9NX1mZB?& zBX@Ob{XbM4(2}lD?O9>>m#1B+Y3ceicL|bl(H+w7qH#aHe}m>&b}(4|q{zrA z67i^d(Joyy{YNsTm}sU?)R*mjS;jH?dIX;EqcCBpT~4D0TKpAiaLqOFtinjtd43x` zRDD^KqRw4Us`35}cbArDFU>LNHpS&V|0-j>yRfu~zoIXnYDXC7JlgYL$!_B!0qm{`);TKqnOB+X5pPAPuC`|#fnlzBr03zhVf7!m8Jn7h@N$5<45;tbN zYlX87$pCHTlDcKMt!fk7{sTkud*G}j(34PAW8Qjmk9-ja-Kg;u2mj6X-)vcR&D4P* z_u%Rie||cJ&*pDtqJP-Qm=$h*y{^~I*Dw}$8#9`tC+HQz8$4tP4!8KUPR5uU2#bbX zX_z*N06nW6D_KBWoAkgB6>&#Ja!Nr*W#4Y>Ia4yQ(h}eGJ>TSPd7|XM(IUMnzI0ih~2^F(K#R1S6!@9^J}ZI|ykXv6pcpR!r5>)54R0_`(}_}Tt>XevGs z4p~{5Tr2Kfp$h|m-0IEIVGRJe>C5&|;2Tb*9P%oU^c4zknqB??&P~2YUODhrYU`_P z0;e3xsFk9?c?h-OVtJx`IRA9r6zSx+?YW59c^ym+bW2lx|O!yGf+$yj7Z99_L1;`jV= zHXZ>F8MIMgI+5@lGY+oSJe_?(9}mvzw82Y7L=By=!(0^0{#(j*qTepHE2}3s(zltT zH;^BS^>VV538gVH(H9?>KR0M1Hq{dX&PV;D^HDWZm=eFxHPa_A4b2*+s}GChxv}wb zb6~rA7k{JQ%+|oo?LQgu8ZaVLOw)wm(&$8cGez>8#EsDYyg|Fzfb`saI2K^o5+ouuinnAID!^r)rzRaM@gk<9z_8$hH((nuMvL)j&d%R!l8O8(5H1R=2 z*`5JH3PZnbDEr78Y)kjx^32J!TEk&!{vah| z^IAZ~z+Hf-9AYC-^`8l&2!U~4PRs6-kiR8~1P|v08{b#Ccf(!(wP;%qRiGHbuS?&F z`YV%B^|*=bC}64BX$=jt@fnv(4}brUHz0}-7{2Pq$;mM?)kUs%^&C_~L)xF06doQ; zJ1ohg*Xt{wYKU*s4h|r~}R7NI)C@<(75Rm0bO^~OXa6kKMpe3h1UR@B? zwn^97$%74p$*iXDxNm=(uJy_Xy0ZaWN*qH7 zGNrV&HN(>&1T0F?=mNPo*m5D=$k=TLc?U#f4yfxuvvx5$#>Ed_JV>Vh9&_a_HKiGS z;Z0f}R>D_Ss-(%6)%9cRPou{ncKFtHx$FUKy!lRCZ)VjT%vmhpkD4gLKrYG!uoL|t z|E?QN|D_QiTdxZ)X%kk%0%$+75mnyq2gARyyXp zUc(Ly_xs(1j4gK0@%WOJprN{PE9I@r?UxgAxEbD9i!b1yTGfz}e>tA0J2hl=v6MSg^rx9C;GXlpJ{ zni8REaN8OP-xm7&^VOv`+twn*|3vaQ6bc4hiTLRV@aorwQq}ZUqvv-~pr^!-^3ROm z_B%w0M&D!5zxcg z7jP}~VA{#Ap+5e;{tuZ4^@(-QKu60`AZ@F}#xn~5r+|&2Gb$PfVkHGb0U+dgpI^xs z3d$ySCNPyRk0;ZYJf@V<7Y*15otpRPh5w#UO&(|zS-$e$WNLfx;NW0L zprhjrrgw!F6i3BKwgAZu{rBJch2U;zva2!Kgb@GILW zUk8-NI1Qujez#y>fnMFNjlI3SglAu}?WJ;;`{Tritki&emy&h&Zcgdl;&?L-7-vBn zJ2iRSchHAX|NajwER&S~xGNo(ojOBa%C!YFG2ujO(r9XZ`Vo3t&Ggi8X7UMWAMy|m zwK2p61=ZDqN~Kq8X8bAW-iiN>{-zhcc8aVvHTj*6(Gg(n0?VBMAAbo(_DfJd8rwv{ zQDMMqa2C`$;6_>yHt@e1{QRlkxa@?1O?tqOg^gWMrOwv7c6GOQm7(D`PFW(p`1^nZ zXh@+kivl&Nv*p$8#K{*U`wk zt~cT~@GE^nB3Or5HT7cpMuh%XCFdH{^qIxspdyL_0;0$zv|ttOf*>j)G^v81Rmxbk zEM)-&%SBtjDndyD2qHlxtS)#dN)#8gRH1<7VoCx;NVz8^fJh*O2oWL)F-QWr?Rn9i z+3C(sXTR`)N%GI1_q@ONB%J42m-oePi_s#{7s`V5o0l#v?kaLLWE{TiMsm~@5+sdx z5{I+Jm6nn_ zTQD6Y-PkVLHP!L=)()oTn3EbeezU@ET=-<}ov9=$a=ZX2&|vcwo;_-fD1En|E< zvi3{Ix)?F`@QwD&zP?``=N(?giO$1ESK4TuTQ4bT>pvcqR?d7mx#+I}=IV-$oBEYCy{FKrS z3lM$s0I9 z44qa5d;xJW%5{p3M|%9uE9lv+Cn}#g6@ORpdz=|27KV(Ob#j=uPFWtjuf5{!wSN}R z{8CObk`nl;`2LVYJq$KQd0ed);aMnMiKFIJm%4-_WnNelD{Je{*roXugRwLtU=D)2 zMDPg^l^=2PFtrJx0*f(p`5o!qd-wFv{mZIr>L2I%7jLcL#eG-Emx}6oik?S2HJmtW z#iH0U;m`5pXZZ3^P*M=v)DwXJHoW~IFQgU_!OqU-Ph!l6Wt7CbH8r4*F?FgX&TYMT z@#0ERS6ka>unspxpKtAzrYJn&xQbBCPAMA2EB&z;IGKRXupOv=kl}?@0ZvdX34^slgf(cu8MBKZBH+`*wnQDXBc$$-%y| zta;OVf7=7iQH1i>s^G#MW3dnaqn*@T23|N*EA(t_L*x69RE3^S=};cZ{}~$&$w4qHTYTUKo69|M?l2jHv38I3 ziHNu_;CX;a$ia^?&D($d^jMq4m7E+IFUHiSEvy)I0Eiuv3{(^<02 zEkwQGn%L%+9BbJ`K&lliW>S0baCqqbR?c;me;=1SBB$!Sv=XV-I438k_UwciZnh%# zc>8rH)PfxRX1fzRrWczuq6d@TA&NQT{5|ii88FMVyxwqV9YGw-FLmF(eF8@fh>MBg z@aOw#Cf9BW-9`&K5?Pouy4*HxM%v{m>Jo=4b`h0KJLi1`hGXBx5v=I|eSjCozDWOXbJ^tRb#RcHL`FtV z|K^bUNPMP+#S*+tHqG`iN77atA_s8 z=~qR)G(-{A%jLeb&4d2>n;kUC4)W~lfsD=qcOi|hiEYFekR9!i80h)}>XO2+b01C; zHdUiof&fDZO8iKJ-ja;n>gF)DzOS$EG$0XXo{18lZC1^AH52~jP+W^;OF8&YXc;MlAVRSFh8FRY5FT%prxuCM|-Vf zGBksX6D4P;DjPc3 zhU`}x3IM5UuubIY!IfYQ3FPK*WdUQ(8sTl|trf~O(&T;`0#1b1eC1LgLgMG%e0_yN zu?m%E2M`zO=F4@L{0ny6d-~MMPW7BM4e*U6VpJ#;iZL%$r&(x^VRu5Q?@JX1q|Te; zX6~5?@LC~$x-F1yCKL*jCVzaevfShBt5tMm^A`L3&l;r)QQZSF*)RjVOdK;{dDuMO zZF$`mqF_Z*?@yunm?TKug{J9-b4XK(e*p$l!%y*qI?ld*eqv58lHIq{EJH@6_#S_E zy@n>$8kHzj8^t5aJ27bk13_LhY;QD-ej1n*2yVa*)Nlkfq6**#dGGE-Nvic0{Zz0(l$L!>(Q?9PASLGh%us-}t zMV8p^xSnlJB7!KRG5-QnaI|_`&A#>N0D2hnby@|7%e{b()K`;}O)p=*Y&r$|LJyOW zkRYB9_VLL?BDd(}2F5lGx7?x(wRMofnI$nvZ(277Y6_wBnIgF$7Z|^$>Vq3HqNt=b zv~yEmy&++94cv#$noAhEl-pk4n+**ODW~F6Y13*jC9z+;xb))13$->1R+8)iMy&%2 z#lR;ij)#XY>h0~-SMi`h;2|)en=oAEGWX+y2L^Ct*9s|~-_bYP7z-EMDKV0IMdPTRqa4kcr4@$P#slm0Oa_->(fl*wNccYe1CilIo zA|R12BZ31D0X?l%Fc^2K8n(BI;2%`=dacY(XBrU^0l*AvBOp{?^W|e(Qr7`@d`C*$ z2w4)ZdD|}xgspvT_nD(dEwn0z&YG&?i2%8)NDzNX)T#I^=rQy#ttZSDPVnauxfV0J z9I|8ByG+9C=DJTy>lKKMjjy|%uYm$I5x}*UrvQ^%Ftpm<96|baH zzu@44fzC~i<^7iput^QCBUhO^{k!!_uWBhq#=PIE|8FKQf`;_fDmi{kVFLe3L;&S(%lG13T(Q&lomGKAt}wKyF*E7kp?%Rgop}T*mQS?O4oO9 zfA9OA^L=N|%zysO<2cF_cdYwb*SgkPFby^NhgjrTXlQ5;6%`;_XlS=f(a>&@-^T#| z^EoFC8~i2WA#32F?QHGgZSM9GP1W4P#lhLb!Perj*Go5dTW2SJE?yok9*)OQ4-Xf2 z5pHhB|J=dl>}JEA>So{u-h%0(VCas9hHH-cbL*XCnJpTcWV|9oO2;R6ch)b~a`lzu zLGAv@h2zd6KN~}m3A9C$+YGANOhigW6#`0WOz)H)6x!XbW>KBiXiQ|$OsZ)5N)z`u zjzEvNFtOrcl7bQ>>UL{l#PGp`!A~E&{radAxcj{moYya}4`RLJx%oxg`6c>~Pw1-~ zBz{i?TKBnMo_^fF5F=MiX28P-KddU19%vBM4;rC19r)40WGY4dvlvT71r`+*r9hmS zNdRt4V&mauMBSikW}$?-p`xoN75qL*N2=BXjZ}t6mLWw^m=XsnKwk2h7Ti}Tk<9S+ zITt+v>{;dPdG-c`xpcsa00Gt6vu}ImwaRs*1;}?-9gV?r9+u~Gn;v|;jMKU3$wTN<7FaE?UvHPA8)ZqqOL5p?&FmIoDJ}g>T9aX}4T*o>M9wIJl<7jAeGLZ%;$xXee zME)LML5=;t>QPr$~a z?G8q$NDz!+-G%He(vEARLonx-@0G`O{?Ph5U(LQL(XHy53o&1Kx_;+>`|94OIpz~` z!VxNApWz~p`ySLCe@af+sY2MwH`jk|dT0!ZqM=3NyfWm-`~R~j|6h;9zgIP+5Vl#A zAQ4UB{1G1~@=muIp7-1<=6EX6QY!`2HWcH@5{_h;_+p;Sdea0R*B2~?lbSrMM1FE? zo{g|;tgteG2*{8(VzEh+5Yxu28L4i%L`7L=n+z}qU7xMLY{sRLh&AJh8aMHKNOQSx zk2pFys`N!W5)!zHjkWpGBm3dxIJWfzu|+bmB$fsdS@k3cgkN6QX4azaKH7mVT&8ml zDliPq^&@0WQ?i7`M3u_BK7pkXI!ie;M+Cwf_4>CqEx#}i(Yc)=-?s)>#wSKWy~?eB zr?5o(ECk;>xIXEm(SPBI9Og}sIA?jzrN3_~Ku+bJ3K1x0Q2g|DpgGb1LZ3e6=l7|& zCp|$&Y`+F=`thjn@o%vysqDA!OHe*_yQ?mF_2&yOuJ|9k=w6F`Q@SKF%dmeve<(y7 ztH9@pLEqr}dAPPjy(8RVY1AY_9s=3c;Y|y=xjI>HUC;kK$lVzm9v+^wtinX7!Zbog zPhV)8DDHPVJgp>n`NuP|>xo;L4qj-_;5uDfSN+G zAl6n)c0s++c>bIu@F0}N?B@DPDMALK{Vvr|N-$8%OQi@&=YNLGl=M*LW<^Jf8Ht-f z?n{{+E_5vy$J0o`btbl5U|?^+jt88~Mlu)p6R3+H5mDmY)6>&?|KUT3FT4h|os35I zp2hKZWJ>ZLDW$(ngS zCG-%pyE4mgu_&o3Ez)Ye02Qlg?TczGEM%ah{@QdR5i8;ruZS}kJy z9Mp@D7JFJ}e6PLgZ_yHWzUv1r>F>v%WFN5slb_`E7RCylbVDRKdcHE>Zr{pQ&b2;c zp(}jRgZi+XU?9PS&aO<+NH-1KYMff~{l3=Zu39DRGD@%v0%>pu2OP@QKyJ_SQqQXY zV-ne)zFB)q2iLZHc5l{FT%+j5vkCBbF;KH54zqgR(WFxr>TP~9O?hE?&#GDUd*3r@ z6|j5}08~}Q>P8}ln+fNsvLolrW{%eRrt{vn1$?kPE8%ay7K1?QqfmE!look9>^nL= zuI3?hMndwknF7gIA?VfKlByl`p}Kk7%$2{+h6Qc$|FHs{Zp>GlmCu zO9(#H^W6O&17>drc?Fm^Kk79WjmD~}^|q6|Co`W6@{KD;x*uT=#uMwMKX$Ar0xn+^ zwRcQ{$ zhhXF?l^^8~GcxksR_$_zXqK0gn9)%i(twH-!srzVl(JHHGWp4Z<%%BpafrZgd6{Z> zRkdVO{{7e|)Ns|R6Xn|{6RF&fb`xos^`BkO92LGEGkWnB8WqJv3Y=cH@RdHpcQ0m@ zfV|AUfwd!Vtsbi{=GSm0TS``c*5D-H$R;sMuXa3ZkI>e}7w! zWf+y$w3BHc+o&wRX{t2*PH*^~u3Rh=U7`8k8(7y@^p&ffX#U8hNF#DBU47pOWxeb_ zuiXJ37(?CBlL*0IeJLP9 z?8@E(76lbRtuelx?5#XKZ7&r^7%Q6a`ByolGCIjLGo6S!${6VWn7$I2&g03h#**dF z{=Q1Z(lj4bk`WaZfikBGX2qXfMygLtIMLDK^hAa8hCBgjTn`6hE}sM2>s3!HS~}K4 zL>61EM=)&L_;)!Tg7+#MvAue=t5wvCk%Fwz5SXrS4H$+B<(V*I64UaZ<3J#HU!zXN zYo&Q|)pqVT8Vu|5zz)}r2jY*I#~}~_)R`$as;rA>R8sW%wx{cDfrZ^?FknLqE0jjy z;j5yKX0e?vEqi-4lETc>mneGea`Hm(8EltiYc+(g8{7gI3Ap8|Bz=dv)Bmbe>5+ph&X3OIT)iBiT z_j%d_XykdyhIMs_HM~?q00PAbc($t1usSoc<<+a(`1nUC?yXy~vih>{ zj~wAQ!ANPubK#undr$%RbTUh!9syA-VCrySO-XD=~tA;rT7HcPvIi!v8~Y<^jBFdXk#67zf+2X@@$KYq$=zF znNu%n&}Bx6rkVnx&k1#zDhehSLw=YgQZ4I($E8r)>Ul0Cab)V6D}X|uXlK-P5NN0` zvh7;miL*2Ga?lcza1_)KtVj%HP{fjzffTEup3^1GE}iZp7Q)qI>7u+#?X+&Ihym*is!9^Ke;!T=YGN)dr&T1V2oW>PiLkwVDA;`I zXYQxzRm4!eIHF2KK;*!UTuZhAat=tJ)%WAD;s%_Jl zr>Yp^Ss>z}yN%j5tkLM(44ON$W6;L;BI3p~mLHBxc^!quhO6|6qa&ge#JSjb9UF}l z7zl*DcF2Ccs!5_Nd@vH!JG|o}RXM^+^nH%saCewO^@HUVRV|>Fh+MdQvJqHsuPmjA$Nk`EXcx!3pW!ESG5V$fp4=9Ug{(+ZzVe{;bAc^WnZZqexyBvnz^u<$wB_rcYa4 zJY24*j+B=Gk!m;k^}{qc#Y{OeZ=rodQ~ECl}iLQ{9Nl`{n;`_q+Pk=17+`QkN0jYrc?y~U4^)H|Hat8))*LGfi z?BnLflmemq;tX}Lb%K1{edO*q}1zux7fsWqxz%C@iRp7oxOO9-WTwro{Y{@6a;YgxvbaqXufB@)J0WvUyIi-T>V zmsY!kz>$EBGM~;8WKJrcdWGW6U>bMJ?_-nlZ$LVRmWPC!<<#IdKP&#>B zMAfcsxeodEZ$d=4{U{rr7ADb%qC&v7@LxBS6c|twiGT`Ge909LCX@`q9CU_D@PX)Y{_>hvuG9oL%QwG? zr&GKf8>`;x)|wxU^5&k-)6l)zc{r*ZeuypyX?H5D$;?(^%2Z)0`TV#LzapFxjBeGT z&A7_rfpz2q988a^tB@&e@U_Vv!#QEhLYEO5Esjb2W>0%nkDBF78IuK$d#E{tXK z7~9db@eZ9FJ!LC5TQ zjH<9?i$7jh_K{cPxZj%#U)uWhEG6Qh{VJW-DxID>O(?qnE_iuScuq+aQ*a^7wnVEN zMz|iWQ~-`LI3Xe*O}H)UGDDK^5nf|#C41JT$nNhdR~%|WVo79_*LV#^R3g){1-eW* zRK7)+yKAkg`ABJ}Qo@mI-x~0LdkRDy8+3rGO0&hg2|iRLy0S`#0^0{G9X%dWj^u1Q}mUWUhLT*Oi}0x~@AionfP@YQht+P;0b7=vQ=K(a!0qlnQ__D<%2H4RyrQ1(KC z$?f@1fjYk)-5+SFVsf~#wEjUU4}w=5)BE`f*m@Z@>PyUpdCTP5bmU6O3_9T)z$()G zt+&6ckUyln)@ZI_h*>pFz0R)5(<6*ClJzQa(feb{iQM|blPT9+gMSdpb;KeJ`r-DXR_g}c7-@d>pOnniY7zz&^IaS^(hTXQ`~YRTj|RLNEyFU)OD!3pWCv@{3$QJV2k5OyR=B{6 z5b33ZZIQ+S>c;AAEd|{~=2Ft+TzgNE7Mb5Jk!$U>hdQ@QkSoLEEfcmf3Vq;3@7q4k zKJ@dCBa9;^)GmC)0OkD-mJ0|XEfy!za#z)u35uiu-%_Ee_-Lo-9o*1v`T;cVhqX&JlMltE>xnQL1broyeO+5A6D{Mb{oirAhQcl;k*EasW+ z2!<>4$=4J)0eI+Br^^vWkL13uE{u^Ax&01yj5Yr<$~ufe@m}>wn!vP+M3i39L?i$G~D&W~t(ct2%7yHf!<_;tyXlT-G$#YgSLs z^qNkg*5G=>o6KOl5M7R3Md#bZKWXs+_TJqadeF}`*`()OuP1fBVr(G(8|Qg24>-*B zM+CbBfLLqWwbuyeOnwY@f8lEV-lUX#^0~?VK^wnjd)Zrs6Ty0-SMd39HpVE&GbCc~ zR|H&HMF`qB+lNK@7wusrka6im?$s5w3516hhe%;94Q8f)x-aqt* znZZ6ibzvH&r&-Vqw_=JW{}?GESk1)eGDBV3Vjaah7 zlIDxIuIBS_fa?Gy@bzA*Ye4Vayegr zgFO5iZ?yRbBXd{HurbVzR+135xp1zvAC^C;Nac8 z&c|gV9VUyixYyXxm}jTd=jcS^;AHB&+mslKo7y;~F<=8OGalmwurk>>Kvbf1?}4O% zPzw=e`(#YRVBc7B>!E9Cw?#NJWw>YqT3&E_By${cSC-4Q!n@ssOM0iFtj$3mu9IjA z8-=z}6zIr-qf8@;29PH8cO^?P!JUV<1RKV-48#Mlv+Da7vA=Z_#(&odM=#9FoU5lm zq?Ti~0{ouYIu6(lkZJ*j?~di_W|&6}ZJ2V0Wuv$mU{WnQDcZYM&guKY@jMbl+X^A%Y2Dflk$k(~%&5 z+>}S1RP)^1KK^F3&`G}1`?lP+o+1X6dq-NDFMx-rARIA2Qf3Yxu`>2KJ_)}Y5KrO>DOtvDjReDd4UFOQ3{ z3&T=ta>LoVjvj%|-i?7V+h&b@`#5`DpXHJ8;VqBZyfgO)@6zrPXnScL3o~7arrB7L?hwfe8X>q?iEZVp1X!ql-+~9G>a{r4a~#qLkrhbQh5+ZId$+ zRzvw4vraln(B^FIR#>SXk=3A%fYEM`H+8*;I0)kIo4Cm~ui3Kken80TetFBb0`#JG z`_Jnn6B{AcI3OAfe==^zQrsrn7TF{{(*ra;s=ecC8}^<_5b?Z0Y+C960B+?kQ=6i= zI#T9T8yc&%K0>2JL;;br+Si@!f9t|06)X$Uic?kWaPAsnRj z6mEgk9h()v(RZ;v?o)3NcIGGOi*Y@tYXYG_0R~i@16EF4#?Py3eDxC!7%JFya1B?D zYZxe21+h?d7*?bzzD-r^2BRa4?;gM{5m*I>I8GBd64#fHq}e(g=Y7@I5f~sk7OCJs zFxU%GKy_9rE}2{dHq}tF@rfx;(2iE>2XiUL85pg%LeM-+355gpy#C5561qsz^RmN65pj;=NhrqKSp$CR z?0K){qW`#x7BL`(yPQgk=?{;B+-8G(kJ`NwmFCXlsibA14ylg7k7i6qWe1xD^=SZ@-Zr~(KDwULz{f66Q-cZ|b`puqNQ!q-WUeFNRhrXB(I zcE{hR#L*Bc4~#n>f5#j^j^7%6d7G#d>rjKR6-U>smsK%P@)z|wcg-;J^@1kzL6{ax##3%MkvH?s!QZdE{+^|Rz3AsTyA z3`fr2Dj%^kuYR5zjVpAeul&&ZK}G|%Igc@(f!O_^9kj2l0%2CtWB%s#*6`>*1I=~5 zYr?s~JV09i=CuyagnB7GOPQzfa4Swn$h%n>BSo8wZU> z^d7X9tEkunkOzs*8fNf-cZ^lM1Sb;E|ERpC z($$8T=M-nEo;IG=VekjRD-R6%8Ta2VL$c-#2K_{|s-ijlAP8ZXCIM+8bow?Qi?Wg3 z(+144!=Zx7wXT_xeQA*6X<9GyEq@Izq;|hgARQ8T(1y6(8U=?wc7leC9csz2)q42! zk=Cs|FZ;p=HRI8*&Z=cMlP3(k@AJK7fPnvP(;JmB1JTjUB1@Ip5ZD+n#nD~kPSw0F>NpG# zX5_nI>osR+n+kXdCUkQ1QsqNcLV%9mWKBX=CGm_DBxXK=Y|bLCqp^)4fh^b+aE%!< z$@1`)WFc)En|7-{1n220aJGq#B^)_5gWzJ6jnL4f9K`rGJY74Pae~E4yhaZ9sagu# z4efG%cmSEZ+&lhpz!sV42x0>Dn)(;qTi>g=xB;O1X{Oj^I^uxI7)qU2B<6exGk>F! znnU5zCkti`zcz0-VSA>)sPVNp!YQ3#niKF3T8@Hn5G3Uj!lm*FZPigP1R@>)bbv4* zNHmTI?ARro4)}eZY072HMCd&bCPr_r&h17ghI4S?OIgWKwzr6nAiS zD|5;J1+@4vqkxSGBicwK+Iw)sU3G@BS&o+iaZfF>tyNTPE1qmWA<`oNY^!L*>pc zKd?v~{j74i0HZu(mR()NKAf^g&8Bed)|k?#E^>L2G5z#8voP*9nz?XU6u-Bk1pFRA zc75=-tYttLKkYsEL>TWDz_W|haq)9Bi2+lWHU{{=t*DeOj5xgqk=$Sq7?m-i8n%2B zGl9>FHXTQ|O#;4kFFs(SoIKlB_xa|LCaFz=1FszNT!CgJu}2gM#yv48}IKF`nU-EXTJ z4KiS*mXJ!fvC8i%Wu-Y@j7|SLzT=uHgIS-TufS}bh=qMhLh$oCt>GdXOwq9NHrqxK z5SF({CpiiwD&^sThrz|cg$v?*5MH(~B3=Q(mQZoW0~kZ1dLM#JbIU4J&-t||h+7%3 zih2c0@57GMXDPb2%H3ZU0a^^ARM>{Zm}Fcnf3^XWT?n9lVchK%?S^c5VTnrE{=*X* z+*t4aVmgTY7R(34r!WY$vsvPd&_Bt|pVRif72==HJ9sZFpvszp#0l+<)`O3U>J8X1 z@$okL-Qgg7671m%0R3ViZ$1*};fc`+4q86}pvM$IxLmQoHh>UMusNaAn;iIJ9d!a( zz|QcJF-NglIs5GzjD+u)gQ(Xackl47LNQ*XP}JqGthrq`Ct`qLtG7jP^SmB37CshT zTM)SJsek4F{*^!U$ynRSDZBCqqjvG+FrZqb&9GsU*n3w8L4zIh$|GT{<$CRt8^_h^ zeUSwVg1niWvs&uzzlSMeEu#|^485G6rDxWp#Q-6rVs2O;B+wfd$g9(jl*^*Jt>nxO zA79qpVOiIsEtWIYfFkV;#67VAt!O7DqT@AaDYje#?g?2=;s&%DK$54OthZD#Urq2v zSSKJ?gY}#fIql0=_+5rLS;&xMz*7U6avMYa?cNTO5|9|+`0tndcw_37iSy{8aAlCC zG{fAhrA_($rTg=cCq+Jjk99X1j9F^DE(8;dAe~Y+JYL3|;iP5)#sJ=v&K;gRa@gqk z_J3{FMKYD5oFqtx)vIg3*xPu3zTlqG@u2%E69ku?>|0t_cH6CS1-5TFVpJ((AbHEPY4V#@!_*>(tg&P#rsAOwK*khA zdt-sksj8J+phf_*q)Lp}^X?i^bi}Cie_{ijFckXe(u#UG{Fkf13A&%pW2~r7&s5q_ z#-O<6WUbWKot4@3ju^-nvj5+o6j-atV4y!$;{M(yyA!)67)!V$Z-i6$XHxxspk zTV%R_e+ft4oF@xxG2+kri;&lHBNq$Io+9tC425-4Pt!iIf0$;<3C){;vJ~}33^VFK ze!u@%DQnNV94LF&UZJGI{`Abr|4FahRxxt3jg)hZq-~#bKv*ejbohh> zB>od)fevvt#|Lt2KV=C;sQ{6kRc~BfO1A_=R1!(Bdn;0s0nm;!4oF>5G#1H`mbsbb z148X7%$U#UgRA=VJ_D=3(3^APDv8}d$;#|x3)s_E^dEhycub`p-PuP|zKE5@?bm|K zB99B1sz5L|qjocH%Sv9AN}HB~3gR_P{6oRR1a zef&&5$p-jaQ^O1I`0o>Wp$dk8pZqMFOW*y$y{@;2B}Wuf-jba>D>%8Mau=!&1%4m| zA6!+sO0&t@^ley5d1|%qCEKwoz`~M0_L`bL=AV1RPc|`>slW)*s1DSfRi(DBPf83d z*rM}-ZT-ta6#2g6)X%~Wy4mq(f(-YssthZ!sf@XaL7)L199JggdQYDfl9{QcUZl4P z;PM}};ECw-1BeAs3v_gQa-d_ax6Ogk6Mq138O>2yzE+Pn{VOhsE6uaGaI^IP(hO4T zH{a%z{CCa3LrD46W$rCXex_1>H30nXl>jvj%d^Zu*p<6&@5X^Mo=L}0nCEgDI-EP4 zm(%fl-hU-;Ug+2$x>9k{F=p@%h?S3pqtLq6jzQY|?W_ZMV+jE?0h;jQAN}CH!D3d- zrzb+V@NVI?C>uh;kw-|oD$nt8hr`I$ZnL_YeTx+urlcubPhVkwsZ_!0T@IiTiG!pf z#Vnsh4omA;8Z>kcDs47j0s=~@r=3`|PtQN8RGNH7)r#ec&%t>J<3BBVBnT}$`rPBu z=P(+;zOL82YdoLx)#XAsO-exO`P)~97E|xiHMxoV&LfOe2HQIU>)q>P)h(zVh=6cFfsFefX;cy&#BABJ8FDPvIQQ57c`=yZ0>a9=~K=etoH98*NJc7Rjt^~n3DadQI3-36}HKNZ} z1G+jO6=^ee`8#2IIxI`#}kRYKVQC8wnMw-=h)vSTc z+Ba9LW#+|$@CDd=#uGJO-aNB^%K~@WO+oF!cEENKC<_E!If`f%QyzFiP&6R><syhNtzM_r6)oDN)KI0+)(lzwgVOsKVV~X7nIw z!gsTFg)JYj%^8FB;7zLraKz~BH==kqqhM$s5c>fWohiYraT7<+G5o=M-MD>-_=YzrhI^r9_APiKRt_8z>{uDxF^Uc7$w3 zKQw~hs?qS~PZ_YI(GG(YTZ78P%Ch;SYVjfNJgJpmmEK3M7&IY)XH`3yvlI)hQP%%W zg0A7BFNjUZMOOVwh5r6mg(g#3f)kZMMzEQ(5@FY#lLc5_-prvqr05kL z?@IbmZlw)};+Nc~!Dlk#J71SUJtOcoA1r}5B8V$w^mFIGYY((lRr)_3jbS`b*7#AX z`J>dlVPXLWS;YDwW0ML0`V`CzWLw#;Y-JOsn!{~p)2%! zbU8Y&$sr4Ec$dNE1cPhK6;_%Azs(+M;ZB+j#z6I#@h@vN7YLf5{N|s`Po?K!`1@Qnh6)kYX%oeaFB@Aqn%E zAGHEVOM2-8ssH5QZnpQ+oA$jA9-v5JI+SsCFE=Ls>Jn(PtbbJv5W8lV@pA)oWQwyx z2Z7eF6ofSx0f0sTfDLV%*H$10y$tICfzV6)89g=1UPhtIDfB#ha^%-8HUFIcAtq{w z=$n6Q78>kpN$P!?1JyWrio5?2FWQCBhKPO?^YPBg0Bp`R4o{3N();EN#7^X=*16`n zC6_o>X}WAiKraEcruE#3o2wf$qN*%QFbCt`-KYab^lPFPqfIOv=Xss|A;EX@%E1Sf zgVWP~bj1Wvp|Obg+04U@UGXgA%xG&86_t}`psYZ;V!EO#D7vx$2;4#hIg_*A5CIb) z@1o#6j#Qd&&c4y7gU?1CdsOfE2lodjRs1uu-1UFI#3|8pBGuXeWU@{K1 z6Q4VCvY-H8Q}>IMLa+Q7`#%JB(FEw}su>F~g)6#@7vb7BCnN5wMQ9wISL_@nej~3K z(|eJ{4v~^*AQ1xu6)$nULc=Ca@}G;iZ#665BpQ($&OiUR4gw-b5J3yyS%euAeChc^ z2Lve)$lxWzf|q_?d2CZri@84l)(V2HD1{%;I@*RCH=uS-i83`AD^R|ASXb#h<5vHY zu^_2Y7`@FoKWT7Y+|~ItB&Elyk=|n=8n9YN5$XT(VoWvk#cbpafvfDM-AhY!->mta z+BTUkFV`jYMT#9AFMlT7?Bga$oE@htmiMl9U%s_>Ie>S@1E>IRCc1@bXvGjScZFp{n zB4XvL`P>NC`wiOLxw_);FmX{UpUtL9>UOw1^pO{=8(%kT(EgbgK^m}*wrt)8=nD1JbIC&agv~36Sa@++{<}V-p&EA4o z5u9UCiYg*5#E+lk3z89#+O}ITp+MRKg4E(UqsHnM>EHLMgd6%b2@qa?FzEdeR|i}i zz(u2U^c8-Y{w4~0mYPzHuT_tU>&zFUy%U~JRkLPi3g)POJf5=m*?1f2+uvZs=>$L0 z`}pfC`<-QqI1KVQ_x2g(l0|!VuN)SyMCDxSGf$xIv~%Bg2I|hBXnJN;lvn7S8DwXR zqVDo|o8J2hXZeKz1c=Zx9~Fz~$m2@&&5K&~ww=t+UH`o`Cgyo`9{&{C(;g{ngichYxbSFuC6lNzY5PT&v7+#A!Z3b86JE~ z{0nCusHN6wGVUyxv+4sXX~^fYni4$-gN=YOpw5pjFK}X?M{MNBuo2FX5EXo#hecBR z-p7?VHQV<>HSmb!(^N1MY%`V$Cg6f)^>Q{_39+OrR4fGk7v#QAW>6v!s0toqV^k=- zUp$8M3Dq*&k@DEcegEaIfEA4r;09iuq&UxHC zwMY*UPSJd|GF~AeA)YxazHTe6wXemXfErZVgZEe{apB?brUH#k((CmTMObO#KClhCVTwkaV?7x6bDN&+{f{-!YEQxfBfbdx0sNmhro^9=b*OILiIus z0;%=086P`UpGLI$Dyr<@^iP>&+{wpvai4v^`55k?v%za@&>O%1a(>V~NqN;j0Vera@p-Qy;3`M`Ov^mR^1O%XAYT0%qI#9OHEz zk`L9vRro-gD`U~EwTZGQ=?8OwE~_R{k_ywJi*Vmh66c~fL3By4M>c1EuhUPd`d|Gl zUbfFoiOTe|z<~A9 zQTuG#&xbRYsKNeMpI9(#69?3>i2eFP&y%I16e-rD)Fm&%e3LdzY4$y0-O;5DOg_dlVx1>vM?AOs8L<;B2A$9!(E@f>_86C|>*g zWc+#Pv~wp_Wo4yI{|(svUS`QNMO1a)=_+>+3{*c7{%!rCe9E`>1@`)X@A+w4y~CZ^ON;9{Y1P;GimxJtbUx=pb(1sY5KaURtlJ!?(tLrXcj_4>ja;d{3RiFg46G& znKR=tqS|uUM{!ttb6$&vrb&b!CwFKQbZm1vvo0aTb4^%qdba|}mhZRMI#-wD-0=vs z4slWkT^`o;stTwurAYCR^(b@0!*D?lj7&djb#TQF(6GWk`GnGM{YF(94oO^am;_xN z{U#p*%QJZgSM2qAz8_A10!<{S!ao%mf)^b|Z?;+2!%$sG&2zr?`3Mo!jyje;J9Ygo=YMSd7w*tJYkf`)?7&p3(xxDQBwYGd&>#6A z0XJ+g@y#@x*HK>csQw>%`FMv`Ib1ZS)>V=jw?R($GysANmyKas|D#INUeeCfUN}z5 zyY+a{y^X7z+5JEvl#kw-ZpNLzToZB~5rb_UG5H(F}^oe@1zE!AR6+q$Ym+QE6RY8Scv7a#ltmBZqj zU^qYRYvD{+-87fMR)RStLi7IGFy~*DI? z)>Z|BBACKCXijA%iBSytEfmCm@Fd@Fh^Fk9&{+?&M6e1|Xe3P_89O_B3-ITMm%oQj zNGAH1U7?NZTzsAT0sEg(#l4*u?VX=N>E-;=LU*Xo@uW@jjDvK8KDFNtFIpF>$7}MC zIp3$S`F`ik6OQ}0Q7omZs;X=Ga3O-isS8xIX@A^)u}`i)0YQ6RQo`u}D+QztGGrd( zb=6)?)h^mdUOc)TjE0sUqlWz!pFgU%D18 zA%0xA$tD`8YX6R^51(=Q{184JbaN7vP$ZwQ6m-2Fv~Y6ay3~U?y6rg`Fn2L`vLr8g zjt|O^^IapAAp(M^HR#&`M1#|qGC4XlPEo_z*^fiu~#Goqa`%qL+kkW@Agr#;Uf${CWVf?9-tr& z>Yz|PxO@9A(Lj|_n_mCQICQ=_cK%RH1KKLVP-lfdGtiNXg8Vwf(hIP=}TXD5WJD=UgahGYwW>#W8@Htf133+{GZ3p?dX^?ow z*>~sLXaF0ieK%-70?*oaPx4f1!$U;u>dy%LaQ^zZ89%1?AQ;#GS$QH@xe)YYSQODg z-oF36pd5Y(j_7d=UxBj zp1Cvk+&gp6&fv!^-+uePPd~rk^BS$8u84RyQ(<9%cQAGc zvcN^cTS4Dj+s)S7&%)CNNzKCB-Pz6C*};;**T&P!!Oc~OSAd_FpPRwn+uPksf{)MT zf8D|B=4r?G-c#QbJmrDAl7SZz5`hKc2f0|b$N>rIsf;q@sg8fP7O}HGQV2$OGvb2FQlL7z$#J+ zSsp%!)c2Iu)g_AUM=EdMqGTwZxS(V4l;H}EuhP5v8eswfuMII=`2YTzZ8$sSP~x@q zNw*A2@J32yLfE>n2pNg16U(G_hE7w+E<1#5D)^<5S=o*Ro1O~NtNnZO*KNahi+A=U zqQMtE3s;6r^OR~6i5Xc6&h~@5xgYs^8 z^QZYCvLcyY*`vo}aw{X55&a?LrirO0yzps*Ly*)R)IR_44Q2%$O2Eg`QYGW|4+~!F zar@)S3!W=x_h${AP+i^L`*S;_R|`jy-+&lVfp}-9Mjy7WPU0doFODzI{r`P!C9WH9 z_IjjKmOjB$8-mor>}cHsPzsMPg7<;Lm4$}Bt8*?JFsM{Wdj9u(YH zTPu!>*H=y#^FZF2TDOeK@o}VnO~2ur-THI0ib9Vn)LpR-%y!0%ihYh=$Mlg=73v9L zyvMqYffRTGK#VJ+E3?+LYN*yW2h!W8Uo+A37{ zuU^dE-LLps!d&v-V7C5GS#VVLA|r_|&-Oe!o%9GM2^lkd|J3sT-#kT(vo~AJ1LY|6{#rm-7)?3(IL$OoBpd5PYv|7%QBaEdIL*4XI^gsWET9 zF>jUZh_xVDd}Q&h$|W}$`v_BG4VKzOIUWAEgj$7#&!J$es(-1m*GHZqPk4!url zPADs~%?|#p%<}H*nQVT3K9nH3n5VRW$#x|ekC^?Qy!nGdS(N;wj(ARALC*a~1*M#0 z$(*Q}5aul2q$8`U4Ao|!sM=z62QGItm2VlQtl zuGXKk^)RSb=1rICXJ>Z-+5BM>s;U`Hc*4@}iLxuEHu3WmQ=<%uJ5W}@^o#EfMLx={ z-roAKPEaU%|Hq9_P7(B5(@m^4MdtyX(oOrAU|OFz_Y1SO!&7gyo=wr0k?{QU8ufrE zV_M5MUl?9LCDVF$ZJ!=9x&I#iOYJ8P+PHKb=6I#)zND`t`IN>0aNa2`v?;9zVX#QU zor<~6KR4WK?QdY`c!pwf=fxyA`X9xd#MrKHC^}BC zSKDdnN+cahV=iOu-eR&oXeC&B4OwBR%#@D%zV_j)vos|}KT|4d3DfQHSZOID)f|GK zw?djjwCK6_ev;^6G3Qs%Wm0J4MY} z4??|}`yw~DyNfpOahlcfCwzcqc{S6hXGuh87pWs7v9Yna?@m5?uBK)n$)ATmSdhYh zS)0lq^y?m%%XE*$;Mw^Y-G1jO?e;=#4p03cH24bx#`%i57?g)J$@F8rJ>*dHn@yrY zlcm_SkW{4pi~B39zO8}7H=>l;`6fJyd08d8-}F12r4q&uWxS7GmIZ4j+u*Laqy6ZY zv}|!rmoBRCm@HZ>tLli!S4>#j9?wU5wLXxr4u0`pi)QDhqtv{9?@9T*{V!*sSy26s zFjzY3D1%+(5dtR9rD)W3;$9Tv`^&I0+V;-_ZlXUsK(8h>-&q~JJ3g+$EV?=|k3D@6T=q@b<8vA&7z$`6;Z$HOF+ z@7~BKesEf`J|LBYRv62@^l6^zISrpI_fS)>YpvShb~c$g)HLsTZDgWb6)+H&ZUtkl z`B=H9pOy2a@11OKcW=fRm2uLLjO^6_fyI5+5oD&4$d`seG4>4qV?(yu=%~Ih)z-I4 z?vE`v-Ul5o`Tn@)4GB|^5Z3!_%8lrZwxp%l+?fvwQ}dd>9s32cg8@fl4J7RkZs{6^n5*3b7Rk-p%IZ9;T=6q z`AZf}%NQKSkhc_)ab*bq{3&xZFVn8IqQVj^{E0wDf5Cka`b1R@L_EjkBD1&4bzkzY z>TLb}v%_7v$_du+VxVRF{ueWaC66b_Cf3B;Qy}-eJ+3A#g^aCjV>KrMPXos(EQxPh zMMF}#llKLDlD}fDBvd9arWC*7>_f4AdE)i_Zl9GTee#n+OQ68Z>ji<}9wM~vB>aTL zVj(#=+UxG}RojN=1&=|n-7dYFMpl+9_cNa`J37TAJlK6qLu^M@(k#aoQK~VJMKK#3 zg{PeSoK{>g_}-0Do?8=hvXu0@$pt!Z_Kn3v*@3xm$F#@KX-T3ORtYJ&ybH&&^lr`Y2#NJQq^+DbwNKpCGgyq!AL(tbswAS~)gc`^r)+Uvl|D=|F_1oNx+JW1v`gl}nW-BrGEy;07 z348v9+0D!4t?-|P{NcJdJl*K;p$3*4mC>_p~J zPf{Qr3ZnnVo68sOmw#SlG^i-!<6GR*9(Dcfpne`A7~RG(ZI$_yd#m<_uoGVZq7Wo9 zGhIUDfuUEXt2y|&;r6zTq19RH9-e*YvZ5Tz(xR53y`iP5maWD#-TUxBqu?VVdpcE? z$NQdJnBNdOWx>!^{U6qhy3KAdry9;{wQ}%1BKIuJ{vkQnK1z?vNTNzXMzX0rR5*fO z_pCx!lSJ-LeWa6R2Z zG%_alAH9^l`n#_P;tcOB(>%+GdW2yGaNC-jXP=zXOH6&(&#HY^A>=Fy0?yfqFZh_p z2cSlI-McZX9(84Q11l5cSWFagl+#)p6I{6Pj4ndex(9Ow0o>0s6$(DOU7RO#=iB+? zw&$;F1ZTE0%*Q<8c>P4o@#6AZQ3|CcdY|)rlPdXi1K-jk11O=gH*=@OQ%#vZLatKX zZ-p-1M!J$T%6}>3oGpAPDVAoEj}B}>l%3H~Dyrr``StkR`^6#E)BEh(2Zx}RpYBVCZ9)EC8u{ju9_%VO`a0fv zFlN0jENRR4qXmz~dygXyJfzijAWZ)SBB&ntYoZe&Cs{Y1$V=6JYtesd+Vp|e;Y-E7 zgvT?uA@?Uk-Y@jH_4|)AN4)ReE%6uyvyK$+rAKOsEG4J;ih^)G{|WlY>f}d2`G^UI zcl)ZB+zAV?3^+QTCQdZ{v|+`!mRUSuwa4|oF%A4b(egdC%WCBU(Z7b}rnE;+L<_A} z6h~EP-`qu3j8Irs0fWH;0U4y2YAGRyQt`6WC}s)@$=;B@==m;ktn@ugJp*zhy>EqT z6*q>I?kvi3EaNuztGeII1$#|(%ie49kl_v(((*H#NJkPHH9e!GuCRcyzL+hg$Z}HFRuu8-$Nd^Fj9nC$}iGQ z*|KpAP8cbo>;F7v`bt-BoDK5!_sSZxeINebWIK=~s`&$^y?spBA7KYZ4U7p1_Q#__ zXbF}7mVfwdto!}5K6%iGrY7Oceb-B;@OLJW1DnAqx0Y28jWQyLO?U`WEme6;bTz-( z5yi6`T}ilh6RKuwD^;0WtT~^?qb7$}ezEwIKpW0SxW7FMYvX1@6RI~uOU&HaanTg_l#$H9zQWxhn~@wEdmC@=v*!TvMH#*J4;wQ zMx_4lw^}l>ebKdrF4UqiXZYsk`-xk!!qMeF2_jZ;A~m>DrxQqP%d+nS%1M4lqU#|M z#{1RZ_y8vtTdtqZ|5GX#*mueZ)pp`|aBHzpPxKV101F=BjV;|{bpP*x7RmO#{D3|HqMN>mcPxc6kMp%(b0i!DCN$1BLg>9|gtpQsU9;zac= zBD2USE1H~WP28~n^m~2u{K39JR=99AU<^*%E%D#iNP&CF*XuZ6&?sw;NeadAEj+FEc<^x}I{+tTC z|Lb0UD=pub*6#>^Wmu1rBRlqYrXfQ;^&t}8Zn7@vrX^jjecM6RfiNz7gTt{treFt0 zX;}U7kr-h#$YhCILDUb$Kj?>+K)H%jVqBcKZ0rgNKiG%Du%B5Lsu8Hn)v>EeJSQij z#e&O(BD&-1hWz(xLcJm&WXFGU;MA(s)=zJ-cf2y$0zI6pFX7iP#${hd&*~>)D05q) zH|6AB4xO(&$6O;pqyA$qK(3`FSJ&cCTwl*d8g&cy-#!Eo5GSr8}&)&UhtS&P`9b|V1DEn z@1(x9!kj}&A1t8Ao3_mn-L!l}p{n zAUg0!6~^;v{~Cvve;-g-e)X((L?6;7Pipzmw*G{lYM|?aag>Lf;dkj14wSy5D8mpZ z54}gJC(mIO@39LN%Cm4a5mHzENbVQF(z^@N%yLVbyLkm~@W04l-lN0}miV@?R6ngf zd~+xI7cJ&%MgUj7+W5)OqP%;I6+NipDnV~44`=ytTDzB^y4XAv&X^*hK7+tHs{MDQ95=OkwgzSH4F|$<+kt;=Xy)p7 zQ&>vtkUQB(JJ-(qnZ6_^^NHxDt->Tzb^I0~C0vhTVBuZ1p7tr;a8K-*?c1esPiLw% zgAJ&}l1?pkc=nta`(UwWIIYAzX87H;TmlE!HNAk5Zqq z%uSrYXG!I`eINq0F~Bc5KI;L$6mYL!f2HWS4BMXX<$+E)P6@@f?i1#{PSWq`GZ{Zz zj!!6<*mu9PQFzMmP(GkXA7DB1`xv2vEn9EF}?f0mr`q-|P+M85yHEP!0tmOTqi)ROg%63?C zd<-6lI_PRXUvc5o0=-(Q0O$CS_7xP;juoq3H@W;*=X4NDb5Tir!tuuuYjRO21mPS4 zy%<&=Lof9LJv>q`-2R}yK1P3?@|Kn4@kp0mOu*q{J+eG^eR!nr~>?=s!Ba285Rn>~;v8^%a#r2K@4QBPPvqnPssf47a0bnPWz3HbX(EB6a zCiNGbKVW3+eE_p^%D7M)c{}s96uk5U%#fX9@^$GHXX)m{n=gq9%#N}~5yiH#tu2d2 z5d0zukusc^+1<&9zD& zTzH+$rW`(i_W(@bc9m)WLKe7V+HL?0*1!pBQr;BR=C@9bpUCViSPh=>)70AwJITSr zEsR}F8O?Y|d6o_vlQg(3F4}m!JZ2;!_2=tARXq8@;mHiO3_FRcx%rC4jA;R}=GMP& z^znc>>ZpJerI2qxOv1*jq9#)2fhzEc>+aQN?cJ}_qvi8^KuP@Yad5;NnjqHCP7|)D zEjf+qMj%fRS)f2or)VLIVc#3CH(I9K&&}Vo8dxUGt1x1kOmGv-9B7bD`_8W2;8=}0 zIsGm3gJY#eBX@`nRf|V2(A~$+q|=fV@FeufT6xBT!R0LLz zk60C+j*glBiH~n>nZhPA;G>8`P5uL=d{>#DIm(o|UE^(BQRu+=4SoH{STdXg#3We~ z5pFTAt(r<)_!@Vo$z5EoK1#&Y=Gj0&Fxg4RQyfKW0$jMYhEi3z56xh?$jjF(2%sz^ zh(O< z{)qtX+kU(p0@uYmo$@I3eNAGVkJqfNC%2une=l|zh9*Iqrk>rid-HrE9u@R8z+xDa z_jiuBVX*1&JP&K~t!esJkBA0Za-XSap>EgA2PF4uckaC+J~L~VRVgu*E8;FfAmXW1 z=)!{o9&*j>iV?xb4*Vka z*<|3_^G{i}mZ+;7pbvW<`2JK}FGAnLcSU{6EZGJCl<)B*iv)TFdjrEO_Xc@~Ei8U# zT3olpu_!z}I;IFz9B|Q&Qh=Z#-I-&>tPs?_i7;D>d#hXA9bV{04^U9>n6g7mdfIN06MQ_)XJ7p zHYUFZ&z2T!m2fqZxfggpVKNKI!+f-r6)Qj&lvSrKj$GZa0}1SLJ#sBqT?TQIoNKcd zfWEOe-z+D#dap4+u`wp%0)fHOA3U?FbP-~~=nlmDoP`$AVl6t17IROgtogHXNg+JI zBdc;RdzsZsk+H$B!qU|hKSB=q_V#vr=fu|~(nLp9f!kCf;SbR47BD`sw@NjwZL?!uy`Qh3Bj(3+5_OSv+8ROg>3Bk@!eBp((eZvLP@>m$ z=l4!+&MkL;fB*5Hi5Kf(m~>sD9Vd915cLa!!@ryT!%KUe2(E-}AD z^vu@j@848a%lbfB9`lV`mm}3XJ9vVRWU7Z0pl}j5GE8y_O2S z{+UM|^#^vMoUPUqsO;avQGd<3i+-4N9!RmZGz-`y%>?|9=t{6xPiN;|W$Q6KAq3a# zqMQ8Ry86h?dcVBEG!oPhobaJdi{Jvy%Sbit&u2Aj@<-cZfsyAmAR>vUb8}%m%ZoIq z^^}QH>YlUFoYESftXhZ{3VS8lm^1Zwm`G@~YyNVyNMtgi^(T`nd&7IjyMvWG7wu6T zj5ilqE0owh#eoVBbR2&U=0-u&xIy(r$ruYhLZG?U`Sb+S`8r5) zqx-;)tKjtxexZq-WuHfL32(4ugK^w#E389Y*(b8VCz z9kLoh?tZeSAa%8!f3d6A8S@5{VD0YuXrOjfCf2h#_}1--kyaU$KlB8bx8tg(5LtvG z&f-ss)ojoP?`TI-^dNO_Hh>o9z13c6i&@wNn=p_F)K7X|IhQZ_?anJfNwh^#_-6WC z%bFLK+k;NXLr=(?{7;xV&fAYH?Cr7W?=J@6LFcYge`l@40)AVU4eW@gms&bpG4Zzj zW`gp*5IGR5z8>SDkiGKl4ZPx^U?tMDyOg&WlvCwl4~03E#;6FmiP>%q+p!pk#EtIM zuBz`m*Ia48@<~fJ?`UsNmQTi=OpYa_rEVU z6G;ntm|2)=p!`(w0$GNk#@}pcf*8#n{laUEL}m{!ZekP@4`743@V|500#&g~@LXT> z*?@W{Y&JUOk8%3Pk6l8eQg7_C12A^HsR*QwpKx_lY;8cvW+d>1FC!Sf3WCg9=|ki0 zehY>R)Ilx)QS5hAlUVF80!3$8amEoei2$}mVP9p#Jw<1$!ag5fd#?tQ1$=mc)&sNq ztC^DzRl(@&>X}{J+Z=Que>A0j4>D8G(zM32He+Nyp$^)t(s* zlNeT`UsErQs&Jb8Rnp;AGy&U^rHGbG$?_q}MU+6Q$C&bDaf1>@_;9|?8^Mm>AF6&j znO#$<6g+%+I}=KaF!JL=zs`juH*+rC7(GdRZqxkrC!jAmrxkH&RDx{i{z~hT5>~4Z zxv+=e{`BQzMf(2KNKY38OGWjXkGmGke`W9Q%mI@nR5#7JRSO_Ik5TFKpMy!v7iFDS zT)`X6^iTa7YyDK=49^rxr7s-7;q`+@j7w1EXgwjc)bdxW8ZB0j|ZD*UGtEk{)bJTj8S43JT&8fKYn{a<9#O@sm~4yiD;hs*%lDK zPY|Y7)%IIuV(asSL-3BOSvODP;_Lg1g!|bOB!!&&n=Jtmkw^`W@YbUaY9M#dA+!7` zKz;&>&)9zxjf{QtF>WfE6^Mj3mddQ0wx{x_mi8qEJ>l=WglEv9N^-`{@2oQjVo?5eMDdn!dy}kV+D)a)=xY-ju=X!C={rKlo zZXTZ1#bz(0R|&vCK#X894csY6!PG~(*te67O$fbN_uoiTbe~WYUBxlGKw(P4LYQvd zO}%gFM}xkN(a_gJMWOS;U2K?9)%Wp!=I$$axYZDd(U^Ti+r-B`i!1>gEMa`j^io6N z^aBq|Y3D34sL6x|naJ?*YnS?2?5LiLu_&2vf{< zb=-yOzf+J(^-`_bSj0IZf3i$n%J?S~y%)NJMsEgS6MU)t5CPo(yd$Svtz5qk1L%q{L zORj$O>Yb~@UW;o>);i=AX{RTV)@p83m{%kIH1U(&Vu9yne+{$ft} z#eB<+M>#n>EF?+inMd#8L+{T*W+R;bAPVZwMNA0*VgnuJQ?#?h{nwK zuuc`>uJjsHfL!j%6~|@j5aS59z}!bJ-AD(%_hO(R(@6T_^0ggzUkRv99CzdJ{ag+@ zs&}`JQ)mtJ_*r=$mAxu}q0f{j;_jKj<0V~Ki$XG1Je=}oq>~>S%7NRrRgONLTMXxh zLwTQ8Kr^HkvS@^eagYWQ=m&ZTWZz02Hlg}0`H*J^?mpT$4M0R*C_x3Ifyd+yE0ZV5 z>EBxMfjUaTsUC@asE9dTtzm_jt%pheUt4uzEJe`LSv+et9%M@1hr8=!Z5F>j%jmq! z&^I>jC4M0l*4!+<=)2%@G2+)@Ww@^ZfgG|&asXR=L`p-X^!*?!licz)m!Cx;tRuG` z`Y1=?J2K<+(uLn8`k<|FrK8e67N~;rSnwQ3N=plpu7Ub z6Vo4Z-|h(GGQ`MHk`+3W<^*^8(jH1*;%c}n_OO_TM;7&bhKn`lPW9z==LZVQXxJd! z+CYFO<{|amf^vmcq+ta`d^0a(&Yw+V#z(JCbU~?&dNsV|7z!8*CMHEEom2-s%dSK) zaHuhxH(nR{ZX~o`NBB=YdZx(;vhZy4NI$tp1%M18ka)I{&vOB6-;5I|qga&5;tC)4 z9=uQ$In?FM4lQ^p9zFrvzdTHoyg*T%`ZY# zkc96!0tk?mmbFjb~f3Mm?M_nJh;NMvh?;?Xnp7V2>o#OU9K|1ScM z>xfxT0k?Uqu9AhUn9S;(@WN>g9;%?(NXuxpQC~9lsfb2EJpx5%J4CeM+2!@>uHR`; z#H_4r%#EytpSofDCf7l+S5bbQjw0l+fwlHFoz^Z-C1AZYM3j5CR7iBOapjWyjSmA& z1UDBDOnt8;5r8ob3!KO8C8GYxqRJwJaoE%)vX_w5`)h^sCrwyso*}P+aC-k@Q*qe{ zeasWBw_%I%)U^7o2Keox~TJ`m7p;(|n|#qRCtAC=Cv6ASg)~75FLe)^!OMCe@=%gwcIO85SGu*R35bEMM84&!u7J8@V)8_n z9EChyb`|*n_Qx{Ky0CMjr-D9#f$O?G-kMaxJNIm>)8wBek?!9xF58oh?JNw(>`M`I z#4DU!uM$LZQ>g`glprDTEKG5A?Lmm7lHnyRw$;i&*|_4U5cFqj3Xp=QX2xY78?NJ8 z>@-cX{O^3!?jD;)aPykG~?Z>b=W^ZSi|za?)l22<4k*01%TOz?()YBz;^q zPBFpQ5Z=JPw`d;ie`}$tr>yDWaZ%IOBi^HWZhs@B3S%#=?C6xxh@d73`s# z&9%G*hm4=ID3{Un*D1Tm_JZhLdjWvx%4g%#O;}S;*Ru^5}=blP;PuClP&Py@#*sj8w&u08fS@ zwf_Y}E}-U{)Z~$`ZC;XDQ<1PZQ6|L#2HEwZ`%~Yr-%Z zVMxY3TLfL~!tlnL&ww-qK^%8Lj(11Q(`R~zOMK&HWCSy`t03@P$@k+%LKq0e&l#vqRX z9O$IARtRFE#F5HD?m4D21>yraLK@964s6rigt~xVI0y2G1~>v|9@~2z?-)@&rttHR zl?!>Qijx0AR$2IZe_AmBbRC;ox1iX0|x<8+@O z!j{V}-0u^l)O{#K{fpgg(b({^IZxO;VYc^}U|)A8x{*puz0Uiy|2{_KydO%x$lMROn!f+oxZX`TcXIJs4MA1qVBSl>Rv%aBZGLPBMv(+Y)jRn5kT@R{OJB z|9hrf_w#bV@EdaadgJ~YL0f*H83qgR&@0j#3=;3Pj&zYkKXFs@9s1C zr+pO*>Ie=#p50Pnd7Xkpq2c(3*csIaNT;0!Gs2+N02WL4-+gOQP0+GDEpG3P`u4Ws zGG2<<3^G4bNu5-K?;q{D+n=3Wt`bnI>MugI0Mi9LY=a;o7ppN*Qd8^a8CXCZnV%06 zAX97&d$;;d36LyudD}i3)Y~$+0InI_Zo~TskZ6JjlLZ%2jo|T_+k#Suz1WwYjCS)S zLuHK6eT}h_v}5)Ed{U6-aQBIcGlB!8QD>0`A5fRN&-(a)zxMW-|rqgaKIAl#`Nwe#TNSspvgSno|{z+?7fwN#`F+zx`b9^EqnO>YUodr`> zZdSl}9$%um1WB{=N6dh0CAL_&G*Y!&%RgxS2gJ2u(#kPEKuczzuMo_dfnpw z!~agT8CSSoT2hg8qW_d0P`g7aMy5lLdOfl04R`cAH+=v5An_aPdL4+`_WC@h<&(cx z70y3*pI<~C;yU^C95fg3jD#zOBONG&x%W=;HD~!TOZ*o~cqpRN$jIy0<=YH7)1k|; zwTY%ITr%O|eF_oYdkDe~Z$9JX1{F`mao+u|;y0>RNtu zQ&a}bl7O3MmnS~b6P7tEe`BR8ec|Nke;Ip**o~`kUEVlz37fq|y7g<=U|W+bX-j3} zpU1tiFmvHwmkK^UYuBBI+;tV73*Y_I=2>rAu@*0Cy`jsF6O1wC?4uiz+n!TB=eJ8| zRkI$Qgzs9feE`;NthN3+8PJI!7PMa-Xut)!>^e=roz*l*Od-Dm&=w6_&~c+-QZVOMpMZ9Q-gt- zzTfO`VKAwunbD3mCeFO|*8VgHyDnePeTYvo?H%z7j0;&;Bs(2(;p48^p_&{i{8N1m zo+69b|9tU5uKy2VNzTZe12ETHj4jC^07<(sdN~`iEYV~(otTB3M8H(lE#D0jjKi#%*_I}4|~BjXjv}nVi=9M*Quoc;1cjf3-yR!-K?LDBqJj#UX1!v6 z4=0=%ICiAfT3?rKaXlxtoU}{1-KVd$CF)R;36z1w-*WHl7Qlob5m7)4H&7_pVQy_RpFL4cw)wlY!~!sX#|P<#wf4db{|TD-NHnte zX7^jl9*Pi--rJSh)B?1O58Ep?{>D6{YSd)L3I2EaOCcKJv?axG{UfC6+AzD8)F%rM8SDBkk=Mt}OWwkm50~6+@ zcrWGg(+q6YLp=4V2XWcPW8KHYQ~<9qln~(~lrJ}YmkA8augH@b`&hDmXU<*nU&;9*ndPf6b%tr1 z=de*}OQlsnV7EK>ybFF$ow&4o5MDxTn(lkgG^dwpDp)OHO5Z01V?>IA&Wl$)Dval$ zMOgoJV0NsSQEFv%eTglaz7ETEpUUj4+g#SYTK$iZg2O(o&i57a?O-s|=eL{#K@lIbxxrJ#-DKPasV)Gn_{NP`MB~<~af2H3>^ieH^7Kb;~gv$+ zX_DDvqb7ep%ZQ`s>jKc9(96`&2T10phmXb#{D*atDj;#g z*T0V7j-av_^n)`At3U4BotyNPMn@TzKKVG87v;DN8#>S2QG0m!pr;!e$)*^YKCyM6 zQ%$Wc?C9|QxvM(p+5Gl7o7IIp1Tu+O%~DTL)peV_A3c~M^EHPoKUDX&m=q4*VdqP}I61_5)8}F3Z-tC1xQ&gC-?fN zuc-ER1_p-}8Hd{9mhi!J8-LS%f1wQ(2#NWkE3S(UF@|jOORZ$q5rTMt^xvy7Bga>j z-!GCmd!&@?JuGWClG%JG>bQ0?K(8o~WqL0p9detc4^-U><{W`aXTA2az^M|s2WX6s zu$fHSDOy&cZx@3lQJpi+aF6;rtj-z1v=89P0Bgmc(gluiSnM|h{UnGir^_qr3%r8g z&+_Hk`8(fQGR`mI%-MC~V5%zqQ4IZ~c(JW08#uhRztHHmup&!=%S3@|dLIo!J2j*= zkmz8yK9C}t6id-S(3~-p1Oo)hG6-u($7Gc6LB!I=CxG|vDE2zp)sGOKT*gaU%>{0fOkYA z4lnM~XseH5pGm)J-a!rsD=i(|^g^0mKE-ad`;5OHlsI@$)US;oK~a!I zr5O+pZFUxkwXV7RJCgje{o@`}dZC4{FWHGdO)^XU?U`U2F3_@ov~(PVY5eaD^TQKq zB`A1YdH?yRQ!woZKAW4%IH>09hAJgO%rzwV?AbGkyOwCkYgsYtM;(}79fJvyr!MHc zQ=Eporadgk5u5$Lo$hm{Mt}1(_-Z2O89zl`AjgL0f48%gok(w+f`gF2x&RyprrQzw z_oP8&%>V6mR^;+#OEv)x7YLVQTnss1^1s{!-up0MFq)YC`0l)uUS3{4k>$b4MZah{ zc>DXi7c$Y3M{UGlqo}B;7hRY#uK+hXv1^eOb#p3vFXY;OHhrdsf7pI*8hpL5(h#UW zDTfPpIj*k?!9!q@MY6XJH-Cy``;wRykC)Fo$Z>FRpagMJOgW?L2xbMW#_ddFF#qN8 z8~UH{0lFT5m|F!O1+B;fR{0Hq3@lj2?QCq28OZC{FZA5+AHp7Trn?A;vx|DO>&KZg zA@jSrnz7Y=P-?`65Ulh`^JoY@(kBNhpp%oJ&hwy)$s!q>lkGt#7Uk~KQP~87;Gd{a zf(R*R4-X>5w!RR&eC5}1ngu0@k}54K!pyn+Zg;$$pI|6W;)p!b+n`^d_2uh3_LDAD zf^zzIHk5!fLjpKID~{qbh;^2?1fDq$gwQ9wwABBwc`I>r%j2m1+)$dvktk;h)jgx2 zfVH?Z=F69-JtyFwOmVONJWV1Z1H}p9n=kY?$cL>*GK-!=JcuvfsGl+IX+K|%)hY?s z=Z@UU38fQV@_skflShW=s`45F_u&J_S9?|KQL;C=Q}T=n;=vdEhyzyM5^+_bw|o8< zt5GL!=x<&WAFL~#MLagGmimvooG8{A%1U+NwzZ!97m0k$ASyOXbe4>EJ%DsON zL@f9IcI7_q+7Y(tY>}o1z2=N_HM$O$T2GdaUXq_LdX4(zgkJq|KoAkNwYA$jJ3C!ifPhWd zM1w#?&cR7rGc1fZd&nYy-)E-3#Jak^Cc=;M09=>U?VkzIRbKwe0B2*vfwq@R8HtJN zvqpE335mw*+b`B)fql$gt;^n@$_`MwwS^&Sk?p;`p3BoMO>QzgGp`vFzg5H#MSg> z89*qugu^sA>h3NO5#a=EXtiO%wI}E1YtBza*{h@-r|wmL>F4pz)$UW@YuYuH3o?|9+Pv96bE(8#dUgq_hrGUOd9tYMK$J1mi@P z`Dyl!j{dPLRWHxV{C7)=juEyJ*%^9!HskSUdt4p=9jrJXp?c>-1PQ60BhxNJeGN#H zpBXDRY$G|c2@^#x){R!K$Xwe_`p>q=vC%W70v-~`Tzt}%XJc}dLs=`AC@M~g0$Vcp Q?-h`g71SY>a^|o92QX$?$^ZZW literal 0 HcmV?d00001 diff --git a/_images/examples_Pulse_Building_Tutorial_9_1.png b/_images/examples_Pulse_Building_Tutorial_9_1.png new file mode 100644 index 0000000000000000000000000000000000000000..edbf4194a1ae9de37acb0c839c28a766d674bb1b GIT binary patch literal 15458 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 GL;o*Zwd3ai literal 0 HcmV?d00001 diff --git a/_modules/broadbean/blueprint.html b/_modules/broadbean/blueprint.html new file mode 100644 index 000000000..84117a04d --- /dev/null +++ b/_modules/broadbean/blueprint.html @@ -0,0 +1,1259 @@ + + + + + + + + broadbean.blueprint - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.blueprint

+# This file is for defining the blueprint object
+
+import functools as ft
+import json
+import re
+import warnings
+from inspect import signature
+
+import numpy as np
+
+from .broadbean import PulseAtoms
+
+
+
+[docs] +class SegmentDurationError(Exception): + pass
+ + + +
+[docs] +class BluePrint: + """ + The class of a waveform to become. + """ + + def __init__( + self, + funlist=None, + argslist=None, + namelist=None, + marker1=None, + marker2=None, + segmentmarker1=None, + segmentmarker2=None, + SR=None, + durslist=None, + ): + """ + Create a BluePrint instance + + Args: + funlist (list): List of functions + argslist (list): List of tuples of arguments + namelist (list): List of names for the functions + marker1 (list): List of marker1 specification tuples + marker2 (list): List of marker2 specifiation tuples + durslist (list): List of durations + + Returns: + BluePrint + """ + # TODO: validate input + + # Sanitising + if funlist is None: + funlist = [] + if argslist is None: + argslist = [] + if namelist is None: + namelist = [] + if durslist is None: + durslist = [] + + # Are the lists of matching lengths? + lenlist = [len(funlist), len(argslist), len(namelist), len(durslist)] + + if any(elem != lenlist[0] for elem in lenlist): + raise ValueError( + f"All input lists must be of same length. Received lengths: {lenlist}" + ) + # Are the names valid names? + for name in namelist: + if not isinstance(name, str): + raise ValueError(f"All segment names must be strings. Received {name}.") + if name != "" and name[-1].isdigit(): + raise ValueError( + "Segment names are not allowed to end" + f" in a number. {name} is " + "therefore not a valid name." + ) + + self._funlist = funlist + + # Make special functions live in the funlist but transfer their names + # to the namelist + # Infer names from signature if not given, i.e. allow for '' names + for ii, name in enumerate(namelist): + if isinstance(funlist[ii], str): + namelist[ii] = funlist[ii] + elif name == "": + namelist[ii] = funlist[ii].__name__ + + # Allow single arguments to be given as not tuples + for ii, args in enumerate(argslist): + if not isinstance(args, tuple): + argslist[ii] = (args,) + self._argslist = argslist + + self._namelist = namelist + namelist = self._make_names_unique(namelist) + + # initialise markers + if marker1 is None: + self.marker1 = [] + else: + self.marker1 = marker1 + if marker2 is None: + self.marker2 = [] + else: + self.marker2 = marker2 + if segmentmarker1 is None: + self._segmark1 = [(0, 0)] * len(funlist) + else: + self._segmark1 = segmentmarker1 + if segmentmarker2 is None: + self._segmark2 = [(0, 0)] * len(funlist) + else: + self._segmark2 = segmentmarker2 + + if durslist is not None: + self._durslist = list(durslist) + else: + self._durslist = None + + self._SR = SR + + @staticmethod + def _basename(string): + """ + Remove trailing numbers from a string. + """ + + if not isinstance(string, str): + raise ValueError( + f"_basename received a non-string input! Got the following: {string}" + ) + + if string == "": + return string + if not (string[-1].isdigit()): + return string + else: + counter = 0 + for ss in string[::-1]: + if ss.isdigit(): + counter += 1 + else: + break + return string[:-counter] + + # lst = [letter for letter in string if not letter.isdigit()] + # return ''.join(lst) + + @staticmethod + def _make_names_unique(lst): + """ + Make all strings in the input list unique + by appending numbers to reoccuring strings + + Args: + lst (list): List of strings. Intended for the _namelist + + """ + + if not isinstance(lst, list): + raise ValueError(f"_make_names_unique received a non-list input! Got {lst}") + + baselst = [BluePrint._basename(lstel) for lstel in lst] + uns = np.unique(baselst) + + for un in uns: + inds = [ii for ii, el in enumerate(baselst) if el == un] + for ii, ind in enumerate(inds): + # Do not append numbers to the first occurence + if ii == 0: + lst[ind] = f"{un}" + else: + lst[ind] = f"{un}{ii + 1}" + + return lst + + @property + def length_segments(self): + """ + Returns the number of segments in the blueprint + """ + return len(self._namelist) + + @property + def duration(self): + """ + The total duration of the BluePrint. If necessary, all the arrays + are built. + """ + waits = "waituntil" in self._funlist + ensavgs = "ensureaverage_fixed_level" in self._funlist + + if not (waits) and not (ensavgs): + return sum(self._durslist) + elif waits and not (ensavgs): + waitdurations = self._makeWaitDurations() + return sum(waitdurations) + elif ensavgs: + # TODO: call the forger + raise NotImplementedError( + "ensureaverage_fixed_level does not exist yet. Cannot proceed" + ) + + @property + def points(self): + """ + The total number of points in the BluePrint. If necessary, + all the arrays are built. + """ + waits = "waituntil" in self._funlist + ensavgs = "ensureaverage_fixed_level" in self._funlist + SR = self.SR + + if SR is None: + raise ValueError( + "No sample rate specified, can not return the number of points." + ) + + if not (waits) and not (ensavgs): + return int(np.round(sum(self._durslist) * SR)) + elif waits and not (ensavgs): + waitdurations = self._makeWaitDurations() + return int(np.round(sum(waitdurations) * SR)) + elif ensavgs: + # TODO: call the forger + raise NotImplementedError( + "ensureaverage_fixed_level does not exist yet. Cannot proceed" + ) + + @property + def durations(self): + """ + The list of durations + """ + return self._durslist + + @property + def SR(self): + """ + Sample rate of the blueprint + """ + return self._SR + + @property + def description(self): + """ + Returns a dict describing the blueprint. + """ + desc = {} # the dict to return + + no_segs = len(self._namelist) + + for sn in range(no_segs): + segkey = f"segment_{sn + 1:02d}" + desc[segkey] = {} + desc[segkey]["name"] = self._namelist[sn] + if self._funlist[sn] == "waituntil": + desc[segkey]["function"] = self._funlist[sn] + else: + funname = str(self._funlist[sn])[1:] + funname = funname[: funname.find(" at")] + desc[segkey]["function"] = funname + desc[segkey]["durations"] = self._durslist[sn] + if desc[segkey]["function"] == "waituntil": + desc[segkey]["arguments"] = {"waittime": self._argslist[sn]} + else: + sig = signature(self._funlist[sn]) + desc[segkey]["arguments"] = dict( + zip(sig.parameters, self._argslist[sn]) + ) + + desc["marker1_abs"] = self.marker1 + desc["marker2_abs"] = self.marker2 + desc["marker1_rel"] = self._segmark1 + desc["marker2_rel"] = self._segmark2 + + return desc + +
+[docs] + def write_to_json(self, path_to_file: str) -> None: + """ + Writes blueprint to JSON file + + Args: + path_to_file: the path to the file to write to ex: + path_to_file/blueprint.json + """ + with open(path_to_file, "w") as fp: + json.dump(self.description, fp, indent=4)
+ + +
+[docs] + @classmethod + def blueprint_from_description(cls, blue_dict): + """ + Returns a blueprint from a description given as a dict + + Args: + blue_dict: a dict in the same form as returned by + BluePrint.description + """ + knowfunctions = { + f"function PulseAtoms.{fun}": getattr(PulseAtoms, fun) + for fun in dir(PulseAtoms) + if "__" not in fun + } + seg_mar_list = list(blue_dict.keys()) + seg_list = [s for s in seg_mar_list if "segment" in s] + bp_sum = cls() + for i, seg in enumerate(seg_list): + seg_dict = blue_dict[seg] + bp_seg = BluePrint() + if seg_dict["function"] == "waituntil": + arguments = blue_dict[seg]["arguments"].values() + arguments = (list(arguments)[0][0],) + bp_seg.insertSegment(i, "waituntil", arguments) + else: + arguments = tuple(blue_dict[seg]["arguments"].values()) + bp_seg.insertSegment( + i, + knowfunctions[seg_dict["function"]], + arguments, + name=re.sub(r"\d", "", seg_dict["name"]), + dur=seg_dict["durations"], + ) + bp_sum = bp_sum + bp_seg + bp_sum.marker1 = blue_dict["marker1_abs"] + bp_sum.marker2 = blue_dict["marker2_abs"] + listmarker1 = blue_dict["marker1_rel"] + listmarker2 = blue_dict["marker2_rel"] + bp_sum._segmark1 = [tuple(mark) for mark in listmarker1] + bp_sum._segmark2 = [tuple(mark) for mark in listmarker2] + return bp_sum
+ + +
+[docs] + @classmethod + def init_from_json(cls, path_to_file: str) -> "BluePrint": + """ + Reads blueprint from JSON file + + Args: + path_to_file: the path to the file to be read ex: + path_to_file/blueprint.json + This function is the inverse of write_to_json + The JSON file needs to be structured as if it was writen + by the function write_to_json + """ + with open(path_to_file) as fp: + data_loaded = json.load(fp) + return cls.blueprint_from_description(data_loaded)
+ + + def _makeWaitDurations(self): + """ + Translate waituntills into durations and return that list. + """ + + if "ensureaverage_fixed_level" in self._funlist: + raise NotImplementedError( + 'There is an "ensureaverage_fixed_level"' + " in this BluePrint. Cannot compute." + ) + + funlist = self._funlist.copy() + durations = self._durslist.copy() + argslist = self._argslist + + no_of_waits = funlist.count("waituntil") + + waitpositions = [ii for ii, el in enumerate(funlist) if el == "waituntil"] + + # Calculate elapsed times + + for nw in range(no_of_waits): + pos = waitpositions[nw] + funlist[pos] = PulseAtoms.waituntil + elapsed_time = sum(durations[:pos]) + wait_time = argslist[pos][0] + dur = wait_time - elapsed_time + if dur < 0: + raise ValueError( + "Inconsistent timing. Can not wait until " + f"{wait_time} at position {pos}." + f" {elapsed_time} elapsed already" + ) + else: + durations[pos] = dur + + return durations + +
+[docs] + def showPrint(self): + """ + Pretty-print the contents of the BluePrint. Not finished. + """ + # TODO: tidy up this method and make it use the description property + + if self._durslist is None: + dl = [None] * len(self._namelist) + else: + dl = self._durslist + + datalists = [self._namelist, self._funlist, self._argslist, dl] + + lzip = zip(*datalists) + + print("Legend: Name, function, arguments, timesteps, durations") + + for ind, (name, fun, args, dur) in enumerate(lzip): + ind_p = ind + 1 + if fun == "waituntil": + fun_p = fun + else: + fun_p = fun.__str__().split(" ")[1] + + list_p = [ind_p, name, fun_p, args, dur] + print('Segment {}: "{}", {}, {}, {}'.format(*list_p)) + print("-" * 10)
+ + +
+[docs] + def changeArg(self, name, arg, value, replaceeverywhere=False): + """ + Change an argument of one or more of the functions in the blueprint. + + Args: + name (str): The name of the segment in which to change an argument + arg (Union[int, str]): Either the position (int) or name (str) of + the argument to change + value (Union[int, float]): The new value of the argument + replaceeverywhere (bool): If True, the same argument is overwritten + in ALL segments where the name matches. E.g. 'gaussian1' will + match 'gaussian', 'gaussian2', etc. If False, only the segment + with exact name match gets a replacement. + + Raises: + ValueError: If the argument can not be matched (either the argument + name does not match or the argument number is wrong). + ValueError: If the name can not be matched. + + """ + # TODO: is there any reason to use tuples internally? + + if replaceeverywhere: + basename = BluePrint._basename + name = basename(name) + nmlst = self._namelist + replacelist = [nm for nm in nmlst if basename(nm) == name] + else: + replacelist = [name] + + # Validation + if name not in self._namelist: + raise ValueError( + "No segment of that name in blueprint." + f" Contains segments: {self._namelist}" + ) + + for name in replacelist: + position = self._namelist.index(name) + function = self._funlist[position] + sig = signature(function) + + # Validation + if isinstance(arg, str): + if arg not in sig.parameters: + raise ValueError( + "No such argument of function " + f"{function.__name__}. Has arguments " + f"{sig.parameters.keys()}." + ) + # Each function has two 'secret' arguments, SR and dur + user_params = len(sig.parameters) - 2 + if isinstance(arg, int) and (arg not in range(user_params)): + raise ValueError( + f"No argument {arg} " + f"of function {function.__name__}." + f" Has {user_params} " + "arguments." + ) + + # allow the user to input single values instead of (val,) + no_of_args = len(self._argslist[position]) + if not isinstance(value, tuple) and no_of_args == 1: + value = (value,) + + if isinstance(arg, str): + for ii, param in enumerate(sig.parameters): + if arg == param: + arg = ii + break + + # Mutating the immutable... + larg = list(self._argslist[position]) + larg[arg] = value + self._argslist[position] = tuple(larg)
+ + +
+[docs] + def changeDuration(self, name, dur, replaceeverywhere=False): + """ + Change the duration of one or more segments in the blueprint + + Args: + name (str): The name of the segment in which to change duration + dur (Union[float, int]): The new duration. + replaceeverywhere (Optional[bool]): If True, the duration(s) + is(are) overwritten in ALL segments where the name matches. + E.g. 'gaussian1' will match 'gaussian', 'gaussian2', + etc. If False, only the segment with exact name match + gets a replacement. + + Raises: + ValueError: If durations are not specified for the blueprint + ValueError: If too many or too few durations are given. + ValueError: If no segment matches the name. + ValueError: If dur is not positive + ValueError: If SR is given for the blueprint and dur is less than + 1/SR. + """ + + if not (isinstance(dur, float)) and not (isinstance(dur, int)): + raise ValueError( + f"New duration must be an int or a float. Received {type(dur)}" + ) + + if replaceeverywhere: + basename = BluePrint._basename + name = basename(name) + nmlst = self._namelist + replacelist = [nm for nm in nmlst if basename(nm) == name] + else: + replacelist = [name] + + # Validation + if name not in self._namelist: + raise ValueError( + "No segment of that name in blueprint." + f" Contains segments: {self._namelist}" + ) + + for name in replacelist: + position = self._namelist.index(name) + + if dur <= 0: + raise ValueError("Duration must be strictly greater than zero.") + + if self.SR is not None: + if dur * self.SR < 1: + raise ValueError( + "Duration too short! Must be at least 1/sample rate." + ) + + self._durslist[position] = dur
+ + +
+[docs] + def setSR(self, SR): + """ + Set the associated sample rate + + Args: + SR (Union[int, float]): The sample rate in Sa/s. + """ + self._SR = SR
+ + +
+[docs] + def setSegmentMarker(self, name, specs, markerID): + """ + Bind a marker to a specific segment. + + Args: + name (str): Name of the segment + specs (tuple): Marker specification tuple, (delay, duration), + where the delay is relative to the segment start + markerID (int): Which marker channel to output on. Must be 1 or 2. + """ + if markerID not in [1, 2]: + raise ValueError(f"MarkerID must be either 1 or 2. Received {markerID}.") + + markerselect = {1: self._segmark1, 2: self._segmark2} + position = self._namelist.index(name) + + # TODO: Do we need more than one bound marker per segment? + markerselect[markerID][position] = specs
+ + +
+[docs] + def removeSegmentMarker(self, name: str, markerID: int) -> None: + """ + Remove all bound markers from a specific segment + + Args: + name (str): Name of the segment + markerID (int): Which marker channel to remove from (1 or 2). + number (int): The number of the marker, in case several markers are + bound to one element. Default: 1 (the first marker). + """ + if markerID not in [1, 2]: + raise ValueError("MarkerID must be either 1 or 2. Received {markerID}.") + + markerselect = {1: self._segmark1, 2: self._segmark2} + try: + position = self._namelist.index(name) + except ValueError: + raise KeyError(f"No segment named {name} in this BluePrint.") + markerselect[markerID][position] = (0, 0)
+ + +
+[docs] + def copy(self): + """ + Returns a copy of the BluePrint + """ + + # Needed because of input validation in __init__ + namelist = [self._basename(name) for name in self._namelist.copy()] + + return BluePrint( + self._funlist.copy(), + self._argslist.copy(), + namelist, + self.marker1.copy(), + self.marker2.copy(), + self._segmark1.copy(), + self._segmark2.copy(), + self._SR, + self._durslist, + )
+ + +
+[docs] + def insertSegment(self, pos, func, args=(), dur=None, name=None, durs=None): + """ + Insert a segment into the bluePrint. + + Args: + pos (int): The position at which to add the segment. Counts like + a python list; 0 is first, -1 is last. Values below -1 are + not allowed, though. + func (function): Function describing the segment. Must have its + duration as the last argument (unless its a special function). + args (Optional[Tuple[Any]]): Tuple of arguments BESIDES duration. + Default: () + dur (Optional[Union[int, float]]): The duration of the + segment. Must be given UNLESS the segment is + 'waituntil' or 'ensureaverage_fixed_level' + name Optional[str]: Name of the segment. If none is given, + the segment will receive the name of its function, + possibly with a number appended. + + Raises: + ValueError: If the position is negative + ValueError: If the name ends in a number + """ + + # Validation + has_ensureavg = ( + "ensureaverage_fixed_level" in self._funlist + or "ensureaverage_fixed_dur" in self._funlist + ) + if func == "ensureaverage_fixed_level" and has_ensureavg: + raise ValueError( + 'Can not have more than one "ensureaverage" segment in a blueprint.' + ) + + if durs is not None: + warnings.warn( + 'Deprecation warning: please specify "dur" rather ' + 'than "durs" when inserting a segment' + ) + if dur is None: + dur = durs + else: + raise ValueError('You can not specify "durs" AND "dur"!') + # Take care of 'waituntil' + + # allow users to input single values + if not isinstance(args, tuple): + args = (args,) + + if pos < -1: + raise ValueError("Position must be strictly larger than -1") + + if name is None or name == "": + if func == "waituntil": + name = "waituntil" + else: + name = func.__name__ + elif isinstance(name, str): + if len(name) > 0: + if name[-1].isdigit(): + raise ValueError("Segment name must not end in a number") + + if pos == -1: + self._namelist.append(name) + self._namelist = self._make_names_unique(self._namelist) + self._funlist.append(func) + self._argslist.append(args) + self._segmark1.append((0, 0)) + self._segmark2.append((0, 0)) + self._durslist.append(dur) + else: + self._namelist.insert(pos, name) + self._namelist = self._make_names_unique(self._namelist) + self._funlist.insert(pos, func) + self._argslist.insert(pos, args) + self._segmark1.insert(pos, (0, 0)) + self._segmark2.insert(pos, (0, 0)) + self._durslist.insert(pos, dur)
+ + +
+[docs] + def removeSegment(self, name): + """ + Remove the specified segment from the blueprint. + + Args: + name (str): The name of the segment to remove. + """ + try: + position = self._namelist.index(name) + except ValueError: + raise KeyError(f"No segment called {name} in blueprint.") + + del self._funlist[position] + del self._argslist[position] + del self._namelist[position] + del self._segmark1[position] + del self._segmark2[position] + del self._durslist[position] + + self._namelist = self._make_names_unique(self._namelist)
+ + + def __add__(self, other): + """ + Add two BluePrints. The second argument is appended to the first + and a new BluePrint is returned. + + Args: + other (BluePrint): A BluePrint instance + + Returns: + BluePrint: A new blueprint. + + Raises: + ValueError: If the input is not a BluePrint instance + """ + if not isinstance(other, BluePrint): + raise ValueError( + f""" + BluePrint can only be added to another Blueprint. + Received an object of type {type(other)} + """ + ) + + nl = [self._basename(name) for name in self._namelist] + nl += [self._basename(name) for name in other._namelist] + al = self._argslist + other._argslist + fl = self._funlist + other._funlist + m1 = self.marker1 + other.marker1 + m2 = self.marker2 + other.marker2 + sm1 = self._segmark1 + other._segmark1 + sm2 = self._segmark2 + other._segmark2 + dl = self._durslist + other._durslist + + new_bp = BluePrint() + + new_bp._namelist = new_bp._make_names_unique(nl.copy()) + new_bp._funlist = fl.copy() + new_bp._argslist = al.copy() + new_bp.marker1 = m1.copy() + new_bp.marker2 = m2.copy() + new_bp._segmark1 = sm1.copy() + new_bp._segmark2 = sm2.copy() + new_bp._durslist = dl.copy() + + if self.SR is not None: + new_bp.setSR(self.SR) + + return new_bp + + def __eq__(self, other): + """ + Compare two blueprints. They are the same iff all + lists are identical. + + Args: + other (BluePrint): A BluePrint instance + + Returns: + bool: whether the two blueprints are identical + + Raises: + ValueError: If the input is not a BluePrint instance + """ + if not isinstance(other, BluePrint): + raise ValueError( + f""" + Blueprint can only be compared to another + Blueprint. + Received an object of type {type(other)} + """ + ) + + if not self._namelist == other._namelist: + return False + if not self._funlist == other._funlist: + return False + if not self._argslist == other._argslist: + return False + if not self.marker1 == other.marker1: + return False + if not self.marker2 == other.marker2: + return False + if not self._segmark1 == other._segmark1: + return False + if not self._segmark2 == other._segmark2: + return False + return True
+ + + +def _subelementBuilder( + blueprint: BluePrint, SR: int, durs: list[float] +) -> dict[str, np.ndarray]: + """ + The function building a blueprint, returning a numpy array. + + This is the core translater from description of pulse to actual data points + All arrays must be made with this function + """ + + # Important: building the element must NOT modify any of the mutable + # inputs, therefore all lists are copied + funlist = blueprint._funlist.copy() + argslist = blueprint._argslist.copy() + namelist = blueprint._namelist.copy() + marker1 = blueprint.marker1.copy() + marker2 = blueprint.marker2.copy() + segmark1 = blueprint._segmark1.copy() + segmark2 = blueprint._segmark2.copy() + + durations = durs.copy() + + no_of_waits = funlist.count("waituntil") + + # handle waituntil by translating it into a normal function + waitpositions = [ii for ii, el in enumerate(funlist) if el == "waituntil"] + + # Calculate elapsed times + + for nw in range(no_of_waits): + pos = waitpositions[nw] + funlist[pos] = PulseAtoms.waituntil + elapsed_time = sum(durations[:pos]) + wait_time = argslist[pos][0] + dur = wait_time - elapsed_time + if dur < 0: + raise ValueError( + "Inconsistent timing. Can not wait until " + f"{wait_time} at position {pos}." + f" {elapsed_time} elapsed already" + ) + else: + durations[pos] = dur + + # When special segments like 'waituntil' and 'ensureaverage' get + # evaluated, the list of durations gets updated. That new list + # is newdurations + + newdurations = np.array(durations) + + # All waveforms must ultimately have an integer number of samples + # Now figure out from the durations what these integers are + # + # The most honest thing to do is to simply round off dur*SR + # and raise an exception if the segment ends up with less than + # two points + + intdurations = np.zeros(len(newdurations), dtype=int) + + for ii, dur in enumerate(newdurations): + int_dur = round(dur * SR) + if int_dur < 2: + raise SegmentDurationError( + "Too short segment detected! " + f'Segment "{namelist[ii]}" at position {ii} ' + f"has a duration of {newdurations[ii]} which at " + f"an SR of {SR:.3E} leads to just {int_dur} " + "point(s). There must be at least " + "2 points in each segment." + "" + ) + else: + intdurations[ii] = int_dur + newdurations[ii] = int_dur / SR + + # The actual forging of the waveform + wf_length = np.sum(intdurations) + parts = [ft.partial(fun, *args) for (fun, args) in zip(funlist, argslist)] + blocks = [p(SR, d) for (p, d) in zip(parts, intdurations)] + output = np.fromiter( + (block for sl in blocks for block in sl), float, count=wf_length + ) + + # now make the markers + time = np.linspace(0, sum(newdurations), wf_length, endpoint=False) + m1 = np.zeros_like(time) + m2 = np.zeros_like(time) + + # update the 'absolute time' marker list with 'relative time' + # (segment bound) markers converted to absolute time + elapsed_times = np.cumsum([0.0] + list(newdurations)) + + for pos, spec in enumerate(segmark1): + if spec[1] != 0: + ontime = elapsed_times[pos] + spec[0] # spec is (delay, duration) + marker1.append((ontime, spec[1])) + for pos, spec in enumerate(segmark2): + if spec[1] != 0: + ontime = elapsed_times[pos] + spec[0] # spec is (delay, duration) + marker2.append((ontime, spec[1])) + msettings = [marker1, marker2] + marks = [m1, m2] + for marker, setting in zip(marks, msettings): + for t, dur in setting: + ind = np.abs(time - t).argmin() + chunk = int(np.round(dur * SR)) + marker[ind : ind + chunk] = 1 + + outdict = { + "wfm": output, + "m1": m1, + "m2": m2, + "time": time, + "newdurations": newdurations, + } + + return outdict +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/broadbean.html b/_modules/broadbean/broadbean.html new file mode 100644 index 000000000..2779de26a --- /dev/null +++ b/_modules/broadbean/broadbean.html @@ -0,0 +1,530 @@ + + + + + + + + broadbean.broadbean - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.broadbean

+import functools as ft
+import logging
+import warnings
+from collections.abc import Callable
+
+import numpy as np
+
+log = logging.getLogger(__name__)
+
+
+
+[docs] +class PulseAtoms: + """ + A class full of static methods. + The basic pulse shapes. + + Any pulse shape function should return a list or an np.array + and have SR, npoints as its final two arguments. + + Rounding errors are a real concern/pain in the business of + making waveforms of short duration (few samples). Therefore, + the PulseAtoms take the number of points rather than the + duration as input argument, so that all ambiguity can be handled + in one place (the _subelementBuilder) + """ + +
+[docs] + @staticmethod + def sine(freq, ampl, off, phase, SR, npts): + time = np.linspace(0, npts / SR, int(npts), endpoint=False) + freq *= 2 * np.pi + return ampl * np.sin(freq * time + phase) + off
+ + +
+[docs] + @staticmethod + def ramp(start, stop, SR, npts): + dur = npts / SR + slope = (stop - start) / dur + time = np.linspace(0, dur, int(npts), endpoint=False) + return slope * time + start
+ + +
+[docs] + @staticmethod + def arb_func(func: Callable, kwargs, SR, npts): + r""" + This function is used to generate an arbitrary waveform from a function. + The function must be of the form f(time, \*\*kwargs) where time is a numpy array and + kwargs is a dict that provides any additional parameters needed for the function. + + Example: + + .. code-block:: python + + func = lambda time, freq, ampl, off, phase: ampl*np.sin(freq*time+phase)+off + kwargs = {'freq': 1e6, 'ampl': 1, 'off': 0, 'phase': 0} + + """ + time = np.linspace(0, npts / SR, int(npts), endpoint=False) + return func(time, **kwargs)
+ + +
+[docs] + @staticmethod + def waituntil(dummy, SR, npts): + # for internal call signature consistency, a dummy variable is needed + return np.zeros(int(npts))
+ + +
+[docs] + @staticmethod + def gaussian(ampl, sigma, mu, offset, SR, npts): + """ + Returns a Gaussian of peak height ampl (when offset==0) + + Is by default centred in the middle of the interval + """ + dur = npts / SR + time = np.linspace(0, dur, int(npts), endpoint=False) + centre = dur / 2 + baregauss = np.exp(-((time - mu - centre) ** 2) / (2 * sigma**2)) + return ampl * baregauss + offset
+ + +
+[docs] + @staticmethod + def gaussian_smooth_cutoff(ampl, sigma, mu, offset, SR, npts): + """ + Returns a Gaussian of peak height ampl (when offset==0) + + Is by default centred in the middle of the interval + + smooth cutoff by making offsetting the Gaussian so endpoint = 0 and normalizing the hight to 1 + """ + dur = npts / SR + time = np.linspace(0, dur, int(npts), endpoint=False) + centre = dur / 2 + baregauss = np.exp(-((time - mu - centre) ** 2) / (2 * sigma**2)) - np.exp( + -((0 - mu - centre) ** 2) / (2 * sigma**2) + ) + normalization = 1 / (1.0 - np.exp(-((0 - mu - centre) ** 2) / (2 * sigma**2))) + return ampl * baregauss / normalization + offset
+
+ + + +
+[docs] +def marked_for_deletion(replaced_by: str | None = None) -> Callable: + """ + A decorator for functions we want to kill. The function still + gets called. + """ + + def decorator(func): + @ft.wraps(func) + def warner(*args, **kwargs): + warnstr = f"{func.__name__} is obsolete." + if replaced_by: + warnstr += f" Please use {replaced_by} insted." + warnings.warn(warnstr) + return func(*args, **kwargs) + + return warner + + return decorator
+ + + +def _channelListSorter(channels: list[str | int]) -> list[str | int]: + """ + Sort a list of channel names. Channel names can be ints or strings. Sorts + ints as being before strings. + """ + intlist: list[str | int] = [] + intlist = [ch for ch in channels if isinstance(ch, int)] + strlist: list[str | int] = [] + strlist = [ch for ch in channels if isinstance(ch, str)] + + sorted_list = sorted(intlist) + sorted(strlist) + + return sorted_list + + +class _AWGOutput: + """ + Class used inside Sequence.outputForAWGFile + + Allows for easy-access slicing to return several valid tuples + for the QCoDeS Tektronix AWG 5014 driver from the same sequence. + + Example: + A sequence, myseq, specifies channels 1, 2, 3, 4. + + out = myseq.outputForAWGFile() + + out[:] <--- tuple with all channels + out[1:3] <--- tuple with channels 1, 2 + out[2] <--- tuple with channel 2 + """ + + def __init__(self, rawpackage, channels): + """ + Rawpackage is a tuple: + (wfms, m1s, m2s, nreps, trig_wait, goto, jump) + + Channels is a list of what the channels were called in their + sequence object whence this instance is created + """ + + self.channels = channels + + self._channels = {} + for ii in range(len(rawpackage[0])): + self._channels[ii] = { + "wfms": rawpackage[0][ii], + "m1s": rawpackage[1][ii], + "m2s": rawpackage[2][ii], + } + self.nreps = rawpackage[3] + self.trig_wait = rawpackage[4] + self.goto = rawpackage[5] + self.jump = rawpackage[6] + + def __getitem__(self, key): + if isinstance(key, int): + if key in self._channels.keys(): + output = ( + [self._channels[key]["wfms"]], + [self._channels[key]["m1s"]], + [self._channels[key]["m2s"]], + self.nreps, + self.trig_wait, + self.goto, + self.jump, + ) + + return output + else: + raise KeyError(f"{key} is not a valid key.") + + if isinstance(key, slice): + start = key.start + if start is None: + start = 0 + + stop = key.stop + if stop is None: + stop = len(self._channels.keys()) + + step = key.step + if step is None: + step = 1 + + indeces = range(start, stop, step) + + wfms = [self._channels[ind]["wfms"] for ind in indeces] + m1s = [self._channels[ind]["m1s"] for ind in indeces] + m2s = [self._channels[ind]["m2s"] for ind in indeces] + + output = (wfms, m1s, m2s, self.nreps, self.trig_wait, self.goto, self.jump) + + return output + + raise KeyError("Key must be int or slice!") +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/element.html b/_modules/broadbean/element.html new file mode 100644 index 000000000..0328a2884 --- /dev/null +++ b/_modules/broadbean/element.html @@ -0,0 +1,848 @@ + + + + + + + + broadbean.element - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.element

+# This file contains the Element definition
+from __future__ import annotations
+
+import json
+from collections.abc import Sequence
+from copy import deepcopy
+
+import numpy as np
+
+from broadbean.blueprint import BluePrint, _subelementBuilder
+
+from .broadbean import PulseAtoms
+
+
+
+[docs] +class ElementDurationError(Exception): + pass
+ + + +
+[docs] +class Element: + """ + Object representing an element. An element is a collection of waves that + are to be run simultaneously. The element consists of a number of channels + that are then each filled with anything of the appropriate length. + """ + + def __init__(self): + # The internal data structure, a dict with key channel number + # Each value is a dict with the following possible keys, values: + # 'blueprint': a BluePrint + # 'channelname': channel name for later use with a Tektronix AWG5014 + # 'array': a dict {'wfm': np.array} (other keys: 'm1', 'm2', etc) + # 'SR': Sample rate. Used with array. + # + # Another dict is meta, which holds: + # 'duration': duration in seconds of the entire element. + # 'SR': sample rate of the element + # These two values are added/updated upon validation of the durations + + self._data = {} + self._meta = {} + +
+[docs] + def addBluePrint(self, channel: str | int, blueprint: BluePrint) -> None: + """ + Add a blueprint to the element on the specified channel. + Overwrites whatever was there before. + """ + if not isinstance(blueprint, BluePrint): + raise ValueError( + "Invalid blueprint given. Must be an instance of the BluePrint class." + ) + + if [] in [ + blueprint._funlist, + blueprint._argslist, + blueprint._namelist, + blueprint._durslist, + ]: + raise ValueError("Received empty BluePrint. Can not proceed.") + + # important: make a copy of the blueprint + newprint = blueprint.copy() + + self._data[channel] = {} + self._data[channel]["blueprint"] = newprint
+ + +
+[docs] + def addFlags(self, channel: str | int, flags: Sequence[str | int]) -> None: + """ + Adds flags for the specified channel. + List of 4 flags, each of which should be 0 or "" for 'No change', 1 or "H" for 'High', + 2 or "L" for 'Low', 3 or "T" for 'Toggle', 4 or "P" for 'Pulse'. + """ + if not isinstance(flags, Sequence): + raise ValueError( + "Flags should be given as a sequence (e.g. a list or a tuple)." + ) + + if len(flags) != 4: + raise ValueError("There should be 4 flags in the list.") + + for cnt, i in enumerate(flags): + if i not in [0, 1, 2, 3, 4, "", "H", "L", "T", "P"]: + raise ValueError( + 'Invalid flag at index {cnt}. Allowed flags are 0 or "" (No change), ' + '1 or "H" (High), 2 or "L" (Low), 3 or "T" (Toggle), ' + '4 or "P" (Pulse).' + ) + + # replace flag aliases with integers + flag_aliases = { + "": 0, + "H": 1, + "L": 2, + "T": 3, + "P": 4, + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + } + flags_int = [flag_aliases[x] for x in flags] + + self._data[channel]["flags"] = flags_int
+ + +
+[docs] + def addArray( + self, channel: int | str, waveform: np.ndarray, SR: int, **kwargs + ) -> None: + """ + Add an array of voltage value to the element on the specified channel. + Overwrites whatever was there before. Markers can be specified via + the kwargs, i.e. the kwargs must specify arrays of markers. The names + can be 'm1', 'm2', 'm3', etc. + + Args: + channel: The channel number + waveform: The array of waveform values (V) + SR: The sample rate in Sa/s + """ + + N = len(waveform) + self._data[channel] = {} + self._data[channel]["array"] = {} + + for name, array in kwargs.items(): + if len(array) != N: + raise ValueError( + "Length mismatch between waveform and " + f"array {name}. Must be same length" + ) + self._data[channel]["array"].update({name: array}) + + self._data[channel]["array"]["wfm"] = waveform + self._data[channel]["SR"] = SR
+ + +
+[docs] + def validateDurations(self): + """ + Check that all channels have the same specified duration, number of + points and sample rate. + """ + + # pick out the channel entries + channels = self._data.values() + + if len(channels) == 0: + raise KeyError("Empty Element, nothing assigned") + + # First the sample rate + SRs = [] + for channel in channels: + if "blueprint" in channel.keys(): + SRs.append(channel["blueprint"].SR) + elif "array" in channel.keys(): + SR = channel["SR"] + SRs.append(SR) + + if not SRs.count(SRs[0]) == len(SRs): + errmssglst = zip(list(self._data.keys()), SRs) + raise ElementDurationError( + "Different channels have different " + "SRs. (Channel, SR): " + f"{list(errmssglst)}" + ) + + # Next the total time + durations = [] + for channel in channels: + if "blueprint" in channel.keys(): + durations.append(channel["blueprint"].duration) + elif "array" in channel.keys(): + length = len(channel["array"]["wfm"]) / channel["SR"] + durations.append(length) + + if None not in SRs: + atol = min(SRs) + else: + atol = 1e-9 + + if not np.allclose(durations, durations[0], atol=atol): + errmssglst = zip(list(self._data.keys()), durations) + raise ElementDurationError( + "Different channels have different " + "durations. (Channel, duration): " + f"{list(errmssglst)}s" + ) + + # Finally the number of points + # (kind of redundant if sample rate and duration match?) + npts = [] + for channel in channels: + if "blueprint" in channel.keys(): + npts.append(channel["blueprint"].points) + elif "array" in channel.keys(): + length = len(channel["array"]["wfm"]) + npts.append(length) + + if not npts.count(npts[0]) == len(npts): + errmssglst = zip(list(self._data.keys()), npts) + raise ElementDurationError( + "Different channels have different " + "npts. (Channel, npts): " + f"{list(errmssglst)}" + ) + + # If these three tests pass, we equip the dictionary with convenient + # info used by Sequence + self._meta["SR"] = SRs[0] + self._meta["duration"] = durations[0]
+ + +
+[docs] + def getArrays(self, includetime: bool = False) -> dict[int, dict[str, np.ndarray]]: + """ + Return arrays of the element. Heavily used by the Sequence. + + Args: + includetime: Whether to include time arrays. They will have the key + 'time'. Time should be included when plotting, otherwise not. + + Returns: + dict: + Dictionary with channel numbers (ints) as keys and forged + blueprints as values. A forged blueprint is a dict with + the mandatory key 'wfm' and optional keys 'm1', 'm2', 'm3' (etc) + and 'time'. + + """ + + outdict = {} + for channel, signal in self._data.items(): + if "array" in signal.keys(): + outdict[channel] = signal["array"] + if includetime and "time" not in signal["array"].keys(): + N = len(signal["array"]["wfm"]) + dur = N / signal["SR"] + outdict[channel]["time"] = np.linspace(0, dur, N) + elif "blueprint" in signal.keys(): + bp = signal["blueprint"] + durs = bp.durations + SR = bp.SR + forged_bp = _subelementBuilder(bp, SR, durs) + outdict[channel] = forged_bp + if "flags" in signal.keys(): + outdict[channel]["flags"] = signal["flags"] + if not includetime: + outdict[channel].pop("time") + outdict[channel].pop("newdurations") + # TODO: should the be a separate bool for newdurations? + + return outdict
+ + + @property + def SR(self): + """ + Returns the sample rate, if well-defined. Else raises + an error about what went wrong. + """ + # Will either raise an error or set self._data['SR'] + self.validateDurations() + + return self._meta["SR"] + + @property + def points(self) -> int: + """ + Returns the number of points of each channel if that number is + well-defined. Else an error is raised. + """ + self.validateDurations() + + # pick out what is on the channels + channels = self._data.values() + + # if validateDurations did not raise an error, all channels + # have the same number of points + for chan in channels: + if not ("array" in chan.keys() or "blueprint" in chan.keys()): + raise ValueError( + f"Neither BluePrint nor array assigned to chan {chan}!" + ) + if "blueprint" in chan.keys(): + return chan["blueprint"].points + else: + return len(chan["array"]["wfm"]) + + else: + # this line is here to make mypy happy; this exception is + # already raised by validateDurations + raise KeyError("Empty Element, nothing assigned") + + @property + def duration(self): + """ + Returns the duration in seconds of the element, if said duration is + well-defined. Else raises an error. + """ + # Will either raise an error or set self._data['SR'] + self.validateDurations() + + return self._meta["duration"] + + @property + def channels(self): + """ + The channels that has something on them + """ + chans = [key for key in self._data.keys()] + return chans + + @property + def description(self): + """ + Returns a dict describing the element. + """ + desc = {} + + for key, val in self._data.items(): + if "blueprint" in val.keys(): + desc[str(key)] = val["blueprint"].description + elif "array" in val.keys(): + desc[str(key)] = "array" + + if "flags" in val.keys(): + desc[str(key)]["flags"] = val["flags"] + + return desc + +
+[docs] + def write_to_json(self, path_to_file: str) -> None: + """ + Writes element to JSON file + + Args: + path_to_file: the path to the file to write to ex: + path_to_file/element.json + """ + with open(path_to_file, "w") as fp: + json.dump(self.description, fp, indent=4)
+ + +
+[docs] + @classmethod + def element_from_description(cls, element_dict): + """ + Returns a blueprint from a description given as a dict + + Args: + element_dict: a dict in the same form as returned by + Element.description + """ + channels_list = list(element_dict.keys()) + elem = cls() + for chan in channels_list: + bp_sum = BluePrint.blueprint_from_description(element_dict[chan]) + elem.addBluePrint(int(chan), bp_sum) + return elem
+ + +
+[docs] + @classmethod + def init_from_json(cls, path_to_file: str) -> Element: + """ + Reads Element from JSON file + + Args: + path_to_file: the path to the file to be read ex: + path_to_file/Element.json + This function is the inverse of write_to_json + The JSON file needs to be structured as if it was writen + by the function write_to_json + """ + with open(path_to_file) as fp: + data_loaded = json.load(fp) + return cls.element_from_description(data_loaded)
+ + +
+[docs] + def changeArg( + self, + channel: str | int, + name: str, + arg: str | int, + value: int | float, + replaceeverywhere: bool = False, + ) -> None: + """ + Change the argument of a function of the blueprint on the specified + channel. + + Args: + channel: The channel where the blueprint sits. + name: The name of the segment in which to change an argument + arg: Either the position (int) or name (str) of + the argument to change + value: The new value of the argument + replaceeverywhere: If True, the same argument is overwritten + in ALL segments where the name matches. E.g. 'gaussian1' will + match 'gaussian', 'gaussian2', etc. If False, only the segment + with exact name match gets a replacement. + + Raises: + ValueError: If the specified channel has no blueprint. + ValueError: If the argument can not be matched (either the argument + name does not match or the argument number is wrong). + """ + + if channel not in self.channels: + raise ValueError(f"Nothing assigned to channel {channel}") + + if "blueprint" not in self._data[channel].keys(): + raise ValueError(f"No blueprint on channel {channel}.") + + bp = self._data[channel]["blueprint"] + + bp.changeArg(name, arg, value, replaceeverywhere)
+ + +
+[docs] + def changeDuration( + self, + channel: str | int, + name: str, + newdur: int | float, + replaceeverywhere: bool = False, + ) -> None: + """ + Change the duration of a segment of the blueprint on the specified + channel + + Args: + channel: The channel holding the blueprint in question + name): The name of the segment to modify + newdur: The new duration. + replaceeverywhere: If True, all segments + matching the base + name given will have their duration changed. If False, only the + segment with an exact name match will have its duration + changed. Default: False. + """ + + if channel not in self.channels: + raise ValueError(f"Nothing assigned to channel {channel}") + + if "blueprint" not in self._data[channel].keys(): + raise ValueError(f"No blueprint on channel {channel}.") + + bp = self._data[channel]["blueprint"] + + bp.changeDuration(name, newdur, replaceeverywhere)
+ + + def _applyDelays(self, delays: list[float]) -> None: + """ + Apply delays to the channels of this element. This function is intended + to be used via a Sequence object. Note that this function changes + the element it is called on. Calling _applyDelays a second will apply + more delays on top of the first ones. + + Args: + delays: A list matching the channels of the Element. If there + are channels=[1, 3], then delays=[1e-3, 0] will delay channel + 1 by 1 ms and channel 3 by nothing. + """ + if len(delays) != len(self.channels): + raise ValueError( + "Incorrect number of delays specified." + " Must match the number of channels." + ) + + if not sum(d >= 0 for d in delays) == len(delays): + raise ValueError("Negative delays not allowed.") + + # The strategy is: + # Add waituntil at the beginning, update all waituntils inside, add a + # zeros segment at the end. + # If already-forged arrays are found, simply append and prepend zeros + + SR = self.SR + maxdelay = max(delays) + + for chanind, chan in enumerate(self.channels): + delay = delays[chanind] + + if "blueprint" in self._data[chan].keys(): + blueprint = self._data[chan]["blueprint"] + + # update existing waituntils + for segpos in range(len(blueprint._funlist)): + if blueprint._funlist[segpos] == "waituntil": + oldwait = blueprint._argslist[segpos][0] + blueprint._argslist[segpos] = (oldwait + delay,) + # insert delay before the waveform + if delay > 0: + blueprint.insertSegment(0, "waituntil", (delay,), "waituntil") + # add zeros at the end + if maxdelay - delay > 0: + blueprint.insertSegment( + -1, PulseAtoms.ramp, (0, 0), dur=maxdelay - delay + ) + + else: + arrays = self._data[chan]["array"] + for name, arr in arrays.items(): + pre_wait = np.zeros(int(delay * SR)) + post_wait = np.zeros(int((maxdelay - delay) * SR)) + arrays[name] = np.concatenate((pre_wait, arr, post_wait)) + +
+[docs] + def copy(self): + """ + Return a copy of the element + """ + new = Element() + new._data = deepcopy(self._data) + new._meta = deepcopy(self._meta) + return new
+ + + def __eq__(self, other): + if not isinstance(other, Element): + return False + elif not self._data == other._data: + return False + elif not self._meta == other._meta: + return False + else: + return True
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/plotting.html b/_modules/broadbean/plotting.html new file mode 100644 index 000000000..bcb5e773d --- /dev/null +++ b/_modules/broadbean/plotting.html @@ -0,0 +1,659 @@ + + + + + + + + broadbean.plotting - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.plotting

+# A little helper module for plotting of broadbean objects
+
+from typing import cast
+
+import matplotlib.axes
+import matplotlib.pyplot as plt
+import numpy as np
+
+from broadbean import BluePrint, Element, Sequence
+from broadbean.sequence import SequenceConsistencyError
+
+# The object we can/want to plot
+BBObject = Sequence | BluePrint | Element
+
+
+
+[docs] +def getSIScalingAndPrefix(minmax: tuple[float, float]) -> tuple[float, str]: + """ + Return the scaling exponent and unit prefix. E.g. (-2e-3, 1e-6) will + return (1e3, 'm') + + Args: + minmax: The (min, max) value of the signal + + Returns: + A tuple of the scaling (inverse of the prefix) and the prefix + string. + + """ + v_max: float = max(map(abs, minmax)) + if v_max == 0: + v_max = 1 + exponent = np.log10(v_max) + prefix = "" + scaling: float = 1 + + if exponent < 0: + prefix = "m" + scaling = 1e3 + if exponent < -3: + prefix = "micro " + scaling = 1e6 + if exponent < -6: + prefix = "n" + scaling = 1e9 + + return (scaling, prefix)
+ + + +def _plot_object_validator(obj_to_plot: BBObject) -> None: + """ + Validate the object + """ + if isinstance(obj_to_plot, Sequence): + proceed = obj_to_plot.checkConsistency(verbose=True) + if not proceed: + raise SequenceConsistencyError + + elif isinstance(obj_to_plot, Element): + obj_to_plot.validateDurations() + + elif isinstance(obj_to_plot, BluePrint): + assert obj_to_plot.SR is not None + + +def _plot_object_forger(obj_to_plot: BBObject, **forger_kwargs) -> dict[int, dict]: + """ + Make a forged sequence out of any object. + Returns a forged sequence. + """ + + if isinstance(obj_to_plot, BluePrint): + elem = Element() + elem.addBluePrint(1, obj_to_plot) + seq = Sequence() + seq.addElement(1, elem) + seq.setSR(obj_to_plot.SR) + + elif isinstance(obj_to_plot, Element): + seq = Sequence() + seq.addElement(1, obj_to_plot) + seq.setSR(obj_to_plot._meta["SR"]) + + elif isinstance(obj_to_plot, Sequence): + seq = obj_to_plot + + forged_seq = seq.forge(includetime=True, **forger_kwargs) + + return forged_seq + + +def _plot_summariser(seq: dict[int, dict]) -> dict[int, dict[str, np.ndarray]]: + """ + Return a plotting summary of a subsequence. + + Args: + seq: The 'content' value of a forged sequence where a + subsequence resides + + Returns: + A dict that looks like a forged element, but all waveforms + are just two points, np.array([min, max]) + """ + + output = {} + + # we assume correctness, all postions specify the same channels + chans = seq[1]["data"].keys() + + minmax = dict(zip(chans, [(0, 0)] * len(chans))) + + for element in seq.values(): + arr_dict = element["data"] + + for chan in chans: + wfm = arr_dict[chan]["wfm"] + if wfm.min() < minmax[chan][0]: + minmax[chan] = (wfm.min(), minmax[chan][1]) + if wfm.max() > minmax[chan][1]: + minmax[chan] = (minmax[chan][0], wfm.max()) + output[chan] = { + "wfm": np.array(minmax[chan]), + "m1": np.zeros(2), + "m2": np.zeros(2), + "time": np.linspace(0, 1, 2), + } + + return output + + +# the Grand Unified Plotter +
+[docs] +def plotter(obj_to_plot: BBObject, **forger_kwargs) -> None: + """ + The one plot function to be called. Turns whatever it gets + into a sequence, forges it, and plots that. + """ + + # TODO: Take axes as input + + # strategy: + # * Validate + # * Forge + # * Plot + + _plot_object_validator(obj_to_plot) + + seq = _plot_object_forger(obj_to_plot, **forger_kwargs) + + # Get the dimensions. + chans = seq[1]["content"][1]["data"].keys() + seqlen = len(seq.keys()) + + def update_minmax(chanminmax, wfmdata, chanind): + (thismin, thismax) = (wfmdata.min(), wfmdata.max()) + if thismin < chanminmax[chanind][0]: + chanminmax[chanind] = [thismin, chanminmax[chanind][1]] + if thismax > chanminmax[chanind][1]: + chanminmax[chanind] = [chanminmax[chanind][0], thismax] + return chanminmax + + # Then figure out the figure scalings + minf: float = -np.inf + inf: float = np.inf + chanminmax: list[tuple[float, float]] = [(inf, minf)] * len(chans) + for chanind, chan in enumerate(chans): + for pos in range(1, seqlen + 1): + if seq[pos]["type"] == "element": + wfmdata = seq[pos]["content"][1]["data"][chan]["wfm"] + chanminmax = update_minmax(chanminmax, wfmdata, chanind) + elif seq[pos]["type"] == "subsequence": + for pos2 in seq[pos]["content"].keys(): + elem = seq[pos]["content"][pos2]["data"] + wfmdata = elem[chan]["wfm"] + chanminmax = update_minmax(chanminmax, wfmdata, chanind) + + fig, axs = plt.subplots(len(chans), seqlen, squeeze=False) + + # ...and do the plotting + for chanind, chan in enumerate(chans): + # figure out the channel voltage scaling + # The entire channel shares a y-axis + + minmax: tuple[float, float] = chanminmax[chanind] + + (voltagescaling, voltageprefix) = getSIScalingAndPrefix(minmax) + voltageunit = voltageprefix + "V" + + for pos in range(seqlen): + ax = cast(matplotlib.axes.Axes, axs[chanind, pos]) + # reduce the tickmark density (must be called before scaling) + ax.locator_params(tight=True, nbins=4, prune="lower") + + if seq[pos + 1]["type"] == "element": + content = seq[pos + 1]["content"][1]["data"][chan] + wfm = content["wfm"] + m1 = content.get("m1", np.zeros_like(wfm)) + m2 = content.get("m2", np.zeros_like(wfm)) + time = content["time"] + newdurs = content.get("newdurations", []) + + else: + arr_dict = _plot_summariser(seq[pos + 1]["content"]) + wfm = arr_dict[chan]["wfm"] + newdurs = [] + + ax.annotate( + "SUBSEQ", + xy=(0.5, 0.5), + xycoords="axes fraction", + horizontalalignment="center", + ) + time = np.linspace(0, 1, 2) # needed for timeexponent + + # Figure out the axes' scaling + timeexponent = np.log10(time.max()) + timeunit = "s" + timescaling: float = 1.0 + if timeexponent < 0: + timeunit = "ms" + timescaling = 1e3 + if timeexponent < -3: + timeunit = "micro s" + timescaling = 1e6 + if timeexponent < -6: + timeunit = "ns" + timescaling = 1e9 + + if seq[pos + 1]["type"] == "element": + ax.plot( + timescaling * time, + voltagescaling * wfm, + lw=3, + color=(0.6, 0.4, 0.3), + alpha=0.4, + ) + + ymax = voltagescaling * chanminmax[chanind][1] + ymin = voltagescaling * chanminmax[chanind][0] + yrange = ymax - ymin + ax.set_ylim((ymin - 0.05 * yrange, ymax + 0.2 * yrange)) + + if seq[pos + 1]["type"] == "element": + # TODO: make this work for more than two markers + + # marker1 (red, on top) + y_m1 = ymax + 0.15 * yrange + marker_on = np.ones_like(m1) + marker_on[m1 == 0] = np.nan + marker_off = np.ones_like(m1) + ax.plot( + timescaling * time, + y_m1 * marker_off, + color=(0.6, 0.1, 0.1), + alpha=0.2, + lw=2, + ) + ax.plot( + timescaling * time, + y_m1 * marker_on, + color=(0.6, 0.1, 0.1), + alpha=0.6, + lw=2, + ) + + # marker 2 (blue, below the red) + y_m2 = ymax + 0.10 * yrange + marker_on = np.ones_like(m2) + marker_on[m2 == 0] = np.nan + marker_off = np.ones_like(m2) + ax.plot( + timescaling * time, + y_m2 * marker_off, + color=(0.1, 0.1, 0.6), + alpha=0.2, + lw=2, + ) + ax.plot( + timescaling * time, + y_m2 * marker_on, + color=(0.1, 0.1, 0.6), + alpha=0.6, + lw=2, + ) + + # If subsequence, plot lines indicating min and max value + if seq[pos + 1]["type"] == "subsequence": + # min: + ax.plot( + time, + np.ones_like(time) * wfm[0], + color=(0.12, 0.12, 0.12), + alpha=0.2, + lw=2, + ) + # max: + ax.plot( + time, + np.ones_like(time) * wfm[1], + color=(0.12, 0.12, 0.12), + alpha=0.2, + lw=2, + ) + + ax.set_xticks([]) + + # time step lines + for dur in np.cumsum(newdurs): + ax.plot( + [timescaling * dur, timescaling * dur], + [ax.get_ylim()[0], ax.get_ylim()[1]], + color=(0.312, 0.2, 0.33), + alpha=0.3, + ) + + # labels + if pos == 0: + ax.set_ylabel(f"({voltageunit})") + if pos == seqlen - 1 and not (isinstance(obj_to_plot, BluePrint)): + newax = ax.twinx() + newax.set_yticks([]) + if isinstance(chan, int): + new_ylabel = f"Ch. {chan}" + elif isinstance(chan, str): + new_ylabel = chan + newax.set_ylabel(new_ylabel) + + if seq[pos + 1]["type"] == "subsequence": + ax.set_xlabel("Time N/A") + else: + ax.set_xlabel(f"({timeunit})") + + # remove excess space from the plot + if not chanind + 1 == len(chans): + ax.set_xticks([]) + if not pos == 0: + ax.set_yticks([]) + fig.subplots_adjust(hspace=0, wspace=0) + + # display sequencer information + if chanind == 0 and isinstance(obj_to_plot, Sequence): + seq_info = seq[pos + 1]["sequencing"] + titlestring = "" + if seq_info["twait"] == 1: # trigger wait + titlestring += "T " + if seq_info["nrep"] > 1: # nreps + titlestring += "\u21bb{} ".format(seq_info["nrep"]) + if seq_info["nrep"] == 0: + titlestring += "\u221e " + if seq_info["jump_input"] != 0: + if seq_info["jump_input"] == -1: + titlestring += "E\u2192 " + else: + titlestring += "E{} ".format(seq_info["jump_input"]) + if seq_info["goto"] > 0: + titlestring += "\u21b1{}".format(seq_info["goto"]) + + ax.set_title(titlestring)
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/ripasso.html b/_modules/broadbean/ripasso.html new file mode 100644 index 000000000..50492c26a --- /dev/null +++ b/_modules/broadbean/ripasso.html @@ -0,0 +1,509 @@ + + + + + + + + broadbean.ripasso - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.ripasso

+# Module providing filter compensation. Developed for use with the broadbean
+# pulse building module, but provides a standalone API
+#
+# The name is (of course) a pun. Ripasso; first a filter, then a compensation,
+# i.e. something that is re-passed. Also not quite an Amarone...
+#
+
+import logging
+
+import numpy as np
+from numpy.fft import fft, fftfreq, ifft
+
+log = logging.getLogger(__name__)
+
+
+
+[docs] +class MissingFrequenciesError(Exception): + pass
+ + + +def _rcFilter(SR, npts, f_cut, kind="HP", order=1, DCgain=0): + """ + Nth order (RC circuit) filter + made with frequencies matching the fft output + """ + + freqs = fftfreq(npts, 1 / SR) + + tau = 1 / f_cut + top = 2j * np.pi + + if kind == "HP": + tf = top * tau * freqs / (1 + top * tau * freqs) + + # now, we have identically zero gain for the DC component, + # which makes the transfer function non-invertible + # + # It is a bit of an open question what DC compensation we want... + + tf[tf == 0] = DCgain # No DC suppression + + elif kind == "LP": + tf = 1 / (1 + top * tau * freqs) + + return tf**order + + +
+[docs] +def applyRCFilter(signal, SR, kind, f_cut, order, DCgain=0): + """ + Apply a simple RC-circuit filter + to signal and return the filtered signal. + + Args: + signal (np.array): The input signal. The signal is assumed to start at + t=0 and be evenly sampled at sample rate SR. + SR (int): Sample rate (Sa/s) of the input signal + kind (str): The type of filter. Either 'HP' or 'LP'. + f_cut (float): The cutoff frequency of the filter (Hz) + order (int): The order of the filter. The first order filter is + applied order times. + DCgain (Optional[float]): The DC gain of the filter. ONLY used by the + high-pass filter. Default: 0. + + Returns: + np.array: + The filtered signal along the original time axis. Imaginary + parts are discarded prior to return. + + Raises: + ValueError: If kind is neither 'HP' nor 'LP' + """ + + if kind not in ["HP", "LP"]: + raise ValueError('Please specify filter type as either "HP" or "LP".') + + N = len(signal) + transfun = _rcFilter(SR, N, f_cut, kind=kind, order=order, DCgain=DCgain) + output = ifft(fft(signal) * transfun) + output = np.real(output) + + return output
+ + + +
+[docs] +def applyInverseRCFilter(signal, SR, kind, f_cut, order, DCgain=1): + """ + Apply the inverse of an RC-circuit filter to a signal and return the + compensated signal. + + Note that a high-pass filter in principle has identically zero DC + gain which requires an infinite offset to compensate. + + Args: + signal (np.array): The input signal. The signal is assumed to start at + t=0 and be evenly sampled at sample rate SR. + SR (int): Sample rate (Sa/s) of the input signal + kind (str): The type of filter. Either 'HP' or 'LP'. + f_cut (float): The cutoff frequency of the filter (Hz) + order (int): The order of the filter. The first order filter is + applied order times. + DCgain (Optional[float]): The DC gain of the filter. ONLY used by the + high-pass filter. Default: 1. + + Returns: + np.array: + The filtered signal along the original time axis. Imaginary + parts are discarded prior to return. + + Raises: + ValueError: If kind is neither 'HP' nor 'LP' + ValueError: If DCgain is zero. + """ + + if kind not in ["HP", "LP"]: + raise ValueError( + 'Wrong filter type. Please specify filter type as either "HP" or "LP".' + ) + + if not DCgain > 0: + raise ValueError("Non-invertible DCgain! Please set DCgain to a finite value.") + + N = len(signal) + transfun = _rcFilter(SR, N, f_cut, order=-order, kind=kind, DCgain=DCgain) + output = ifft(fft(signal) * transfun) + output = np.real(output) + + return output
+ + + +
+[docs] +def applyCustomTransferFunction(signal, SR, tf_freqs, tf_amp, invert=False): + """ + Apply custom transfer function + + Given a signal, its sample rate, and a provided transfer function, apply + the transfer function to the signal. + + Args: + signal (np.array): A numpy array containing the signal + SR (int): The sample rate of the signal (Sa/s) + tf_freqs (np.array): The frequencies of the transfer function. Must + be monotonically increasing. + tf_amp (np.array): The amplitude of the transfer function. Must be + dimensionless. + invert (Optional[bool]): If True, the inverse transfer function is + applied. Default: False. + + Returns: + np.array: + The modified signal. + """ + + npts = len(signal) + + # validate tf_freqs + + df = np.diff(tf_freqs).round(6) + + if not np.sum(df > 0) == len(df): + raise ValueError( + "Invalid transfer function freq. axis. " + "Frequencies must be monotonically increasing." + ) + + if not tf_freqs[-1] >= SR / 2: + # TODO: think about whether this is a problem + # What is the desired behaviour for high frequencies if nothing + # is specified? I guess NOOP, i.e. the transfer func. is 1 + raise MissingFrequenciesError( + "Supplied transfer function does not " + "specify frequency response up to the " + "Nyquist frequency of the signal." + ) + + if not tf_freqs[0] == 0: + # what to do in this case? Extrapolate 1s? Make the user do this? + pass + + # Step 1: resample to fftfreq type axis + freqax = fftfreq(npts, 1 / SR) + freqax_pos = freqax[: npts // 2] + freqax_neg = freqax[npts // 2 :] + + resampled_pos = np.interp(freqax_pos, tf_freqs, tf_amp) + resampled_neg = np.interp(-freqax_neg[::-1], tf_freqs, tf_amp) + + transferfun = np.concatenate((resampled_pos, resampled_neg[::-1])) + + # Step 2: Apply transfer function + if invert: + power = -1 + else: + power = 1 + + signal_filtered = ifft(fft(signal) * (transferfun**power)) + imax = np.imag(signal_filtered).max() + log.debug( + "Applying custom transfer function. Discarding imag parts " + f"no larger than {imax}" + ) + signal_filtered = np.real(signal_filtered) + + return signal_filtered
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/sequence.html b/_modules/broadbean/sequence.html new file mode 100644 index 000000000..e5b79b50e --- /dev/null +++ b/_modules/broadbean/sequence.html @@ -0,0 +1,1677 @@ + + + + + + + + broadbean.sequence - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.sequence

+# this file defines the sequence object
+# along with a few helpers
+import json
+import logging
+import warnings
+from copy import deepcopy
+from typing import Any, cast
+
+import numpy as np
+from schema import Optional, Or, Schema
+
+from broadbean.blueprint import BluePrint
+from broadbean.element import Element  # TODO: change import to element.py
+from broadbean.ripasso import applyInverseRCFilter
+
+from .broadbean import (
+    PulseAtoms,
+    _AWGOutput,
+    _channelListSorter,  # TODO: change import to helpers.py
+)
+
+log = logging.getLogger(__name__)
+
+fs_schema = Schema(
+    {
+        int: {
+            "type": Or("subsequence", "element"),
+            "content": {
+                int: {
+                    "data": {Or(str, int): {str: np.ndarray}},
+                    Optional("sequencing"): {Optional(str): int},
+                }
+            },
+            "sequencing": {Optional(str): int},
+        }
+    }
+)
+
+
+
+[docs] +class SequencingError(Exception): + pass
+ + + +
+[docs] +class SequenceConsistencyError(Exception): + pass
+ + + +
+[docs] +class InvalidForgedSequenceError(Exception): + pass
+ + + +
+[docs] +class SequenceCompatibilityError(Exception): + pass
+ + + +
+[docs] +class SpecificationInconsistencyError(Exception): + pass
+ + + +
+[docs] +class Sequence: + """ + Sequence object + """ + + def __init__(self): + """ + Not much to see here... + """ + + # the internal data structure, a dict with tuples as keys and values + # the key is sequence position (int), the value is element (Element) + # or subsequence (Sequence) + self._data = {} + + # Here goes the sequencing info. Key: position + # value: dict with keys 'twait', 'nrep', 'jump_input', + # 'jump_target', 'goto' + # + # the sequencing is filled out automatically with default values + # when an element is added + # Note that not all output backends use all items in the list + self._sequencing = {} + + # The dictionary to store AWG settings + # Keys will include: + # 'SR', 'channelX_amplitude', 'channelX_offset', 'channelX_filter' + self._awgspecs = {} + + # The metainfo to be extracted by measurements + # todo: I'm pretty sure this is obsolete now that description exists + self._meta = {} + + # some backends (seqx files) allow for a sequence to have a name + # we make the name a property of the sequence + self._name = "" + + def __eq__(self, other): + if not isinstance(other, Sequence): + return False + elif not self._data == other._data: + return False + elif not self._meta == other._meta: + return False + elif not self._awgspecs == other._awgspecs: + return False + elif not self._sequencing == other._sequencing: + return False + else: + return True + + def __add__(self, other): + """ + Add two sequences. + Return a new sequence with is the right argument appended to the + left argument. + """ + + # Validation + if not self.checkConsistency(): + raise SequenceConsistencyError("Left hand sequence inconsistent!") + if not other.checkConsistency(): + raise SequenceConsistencyError("Right hand sequence inconsistent!") + + if not self._awgspecs == other._awgspecs: + raise SequenceCompatibilityError( + "Incompatible sequences: different AWGspecifications." + ) + + newseq = Sequence() + N = len(self._data) + + newdata1 = {key: self.element(key).copy() for key in self._data.keys()} + newdata2 = {key + N: other.element(key).copy() for key in other._data.keys()} + newdata1.update(newdata2) + + newseq._data = newdata1 + + newsequencing1 = { + key: self._sequencing[key].copy() for key in self._sequencing.keys() + } + newsequencing2 = dict() + + for key, item in other._sequencing.items(): + newitem = item.copy() + # update goto and jump according to new sequence length + if newitem["goto"] > 0: + newitem["goto"] += N + if newitem["jump_target"] > 0: + newitem["jump_target"] += N + newsequencing2.update({key + N: newitem}) + + newsequencing1.update(newsequencing2) + + newseq._sequencing = newsequencing1 + + newseq._awgspecs = other._awgspecs.copy() + + return newseq + +
+[docs] + def copy(self): + """ + Returns a copy of the sequence. + """ + newseq = Sequence() + newseq._data = deepcopy(self._data) + newseq._meta = deepcopy(self._meta) + newseq._awgspecs = deepcopy(self._awgspecs) + newseq._sequencing = deepcopy(self._sequencing) + + return newseq
+ + +
+[docs] + def setSequenceSettings(self, pos, wait, nreps, jump, goto): + """ + Set the sequence setting for the sequence element at pos. + + Args: + pos (int): The sequence element (counting from 1) + wait (int): The wait state specifying whether to wait for a + trigger. 0: OFF, don't wait, 1: ON, wait. For some backends, + additional integers are allowed to specify the trigger input. + 0 always means off. + nreps (int): Number of repetitions. 0 corresponds to infinite + repetitions + jump (int): Event jump target, the position of a sequence element. + If 0, the event jump state is off. + goto (int): Goto target, the position of a sequence element. + 0 means next. + """ + + warnings.warn( + "Deprecation warning. This function is only compatible " + "with AWG5014 output and will be removed. " + "Please use the specific setSequencingXXX methods." + ) + + # Validation (some validation 'postponed' and put in checkConsistency) + # + # Because of different compliances for different backends, + # most validation of these settings is deferred and performed + # in the outputForXXX methods + + self._sequencing[pos] = { + "twait": wait, + "nrep": nreps, + "jump_target": jump, + "goto": goto, + "jump_input": 0, + }
+ + +
+[docs] + def setSequencingTriggerWait(self, pos: int, wait: int) -> None: + """ + Set the trigger wait for the sequence element at pos. For + AWG 5014 out, this can be 0 or 1, For AWG 70000A output, this + can be 0, 1, 2, or 3. + + Args: + pos: The sequence element (counting from 1) + wait: The wait state/input depending on backend. + """ + self._sequencing[pos]["twait"] = wait
+ + +
+[docs] + def setSequencingNumberOfRepetitions(self, pos: int, nrep: int) -> None: + """ + Set the number of repetitions for the sequence element at pos. + + Args: + pos: The sequence element (counting from 1) + nrep: The number of repetitions (0 means infinite) + """ + self._sequencing[pos]["nrep"] = nrep
+ + +
+[docs] + def setSequencingEventInput(self, pos: int, jump_input: int) -> None: + """ + Set the event input for the sequence element at pos. This setting is + ignored by the AWG 5014. + + Args: + pos: The sequence element (counting from 1) + jump_input: The input specifier, 0 for off, + 1 for 'TrigA', 2 for 'TrigB', 3 for 'Internal'. + """ + self._sequencing[pos]["jump_input"] = jump_input
+ + +
+[docs] + def setSequencingEventJumpTarget(self, pos: int, jump_target: int) -> None: + """ + Set the event jump target for the sequence element at pos. + + Args: + pos: The sequence element (counting from 1) + jump_target: The sequence element to jump to (counting from 1) + """ + self._sequencing[pos]["jump_target"] = jump_target
+ + +
+[docs] + def setSequencingGoto(self, pos: int, goto: int) -> None: + """ + Set the goto target (which element to play after the current one ends) + for the sequence element at pos. + + Args: + pos: The sequence element (counting from 1) + goto: The position of the element to play. 0 means 'next in line' + """ + self._sequencing[pos]["goto"] = goto
+ + +
+[docs] + def setSR(self, SR): + """ + Set the sample rate for the sequence + """ + self._awgspecs["SR"] = SR
+ + +
+[docs] + def setChannelVoltageRange(self, channel, ampl, offset): + """ + Assign the physical voltages of the channel. This is used when making + output for .awg files. The corresponding parameters in the QCoDeS + AWG5014 driver are called chXX_amp and chXX_offset. Please ensure that + the channel in question is indeed in ampl/offset mode and not in + high/low mode. + + Args: + channel (int): The channel number + ampl (float): The channel peak-to-peak amplitude (V) + offset (float): The channel offset (V) + """ + warnings.warn( + "Deprecation warning. This function is deprecated." + " Use setChannelAmplitude and SetChannelOffset " + "instead." + ) + + keystr = f"channel{channel}_amplitude" + self._awgspecs[keystr] = ampl + keystr = f"channel{channel}_offset" + self._awgspecs[keystr] = offset
+ + +
+[docs] + def setChannelAmplitude(self, channel: int | str, ampl: float) -> None: + """ + Assign the physical voltage amplitude of the channel. This is used + when making output for real instruments. + + Args: + channel: The channel number + ampl: The channel peak-to-peak amplitude (V) + """ + keystr = f"channel{channel}_amplitude" + self._awgspecs[keystr] = ampl
+ + +
+[docs] + def setChannelOffset(self, channel: int | str, offset: float) -> None: + """ + Assign the physical voltage offset of the channel. This is used + by some backends when making output for real instruments + + Args: + channel: The channel number/name + offset: The channel offset (V) + """ + keystr = f"channel{channel}_offset" + self._awgspecs[keystr] = offset
+ + +
+[docs] + def setChannelDelay(self, channel: int | str, delay: float) -> None: + """ + Assign a delay to a channel. This is used when making output for .awg + files. Use the delay to compensate for cable length differences etc. + Zeros are prepended to the waveforms to delay them and correspondingly + appended to non (or less) delayed channels. + + Args: + channel: The channel number/name + delay: The required delay (s) + + Raises: + ValueError: If a non-integer or non-non-negative channel number is + given. + """ + + self._awgspecs[f"channel{channel}_delay"] = delay
+ + +
+[docs] + def setChannelFilterCompensation( + self, + channel: str | int, + kind: str, + order: int = 1, + f_cut: float | None = None, + tau: float | None = None, + ) -> None: + """ + Specify a filter to compensate for. + + The specified channel will get a compensation (pre-distorion) to + compensate for the specified frequency filter. Just to be clear: + the INVERSE transfer function of the one you specify is applied. + Only compensation for simple RC-circuit type high pass and low + pass is supported. + + Args: + channel: The channel to apply this to. + kind: Either 'LP' or 'HP' + order: The order of the filter to compensate for. + May be negative. Default: 1. + f_cut: The cut_off frequency (Hz). + tau): The time constant (s). Note that + tau = 1/f_cut and that only one of the two can be specified. + + Raises: + ValueError: If kind is not 'LP' or 'HP' + ValueError: If order is not an int. + SpecificationInconsistencyError: If both f_cut and tau are given. + """ + + if kind not in ["HP", "LP"]: + raise ValueError( + 'Filter kind must either be "LP" (low pass) or "HP" (high pass).' + ) + if not isinstance(order, int): + raise ValueError("Filter order must be an integer.") + if (f_cut is not None) and (tau is not None): + raise SpecificationInconsistencyError( + "Can not specify BOTH a time constant and a cut-off frequency." + ) + + keystr = f"channel{channel}_filtercompensation" + self._awgspecs[keystr] = { + "kind": kind, + "order": order, + "f_cut": f_cut, + "tau": tau, + }
+ + +
+[docs] + def addElement(self, position: int, element: Element) -> None: + """ + Add an element to the sequence. Overwrites previous values. + + Args: + position (int): The sequence position of the element (lowest: 1) + element (Element): An element instance + + Raises: + ValueError: If the element has inconsistent durations + """ + + # Validation + element.validateDurations() + + # make a new copy of the element + newelement = element.copy() + + # Data mutation + self._data.update({position: newelement}) + + # insert default sequencing settings + self._sequencing[position] = { + "twait": 0, + "nrep": 1, + "jump_input": 0, + "jump_target": 0, + "goto": 0, + }
+ + +
+[docs] + def addSubSequence(self, position: int, subsequence: "Sequence") -> None: + """ + Add a subsequence to the sequence. Overwrites anything previously + assigned to this position. The subsequence can not contain any + subsequences itself. + + Args: + position: The sequence position (starting from 1) + subsequence: The subsequence to add + """ + if not isinstance(subsequence, Sequence): + raise ValueError( + "Subsequence must be a sequence object. " + "Received object of type " + f"{type(subsequence)}." + ) + + for elem in subsequence._data.values(): + if isinstance(elem, Sequence): + raise ValueError("Subsequences can not contain subsequences.") + + if subsequence.SR != self.SR: + raise ValueError( + "Subsequence SR does not match (main) sequence SR" + f". ({subsequence.SR} and {self.SR})." + ) + + self._data[position] = subsequence.copy() + + self._sequencing[position] = { + "twait": 0, + "nrep": 1, + "jump_input": 0, + "jump_target": 0, + "goto": 0, + }
+ + +
+[docs] + def checkConsistency(self, verbose=False): + """ + Checks wether the sequence can be built, i.e. wether all elements + have waveforms on the same channels and of the same length. + """ + # TODO: Give helpful info if the check fails + + try: + self._awgspecs["SR"] + except KeyError: + raise KeyError("No sample rate specified. Can not perform check") + + # First check that all sample rates agree + # Since all elements are validated on input, the SR exists + SRs = [elem.SR for elem in self._data.values()] + if SRs == []: # case of empty Sequence + SRs = [None] + if SRs.count(SRs[0]) != len(SRs): + failmssg = "checkConsistency failed: inconsistent sample rates." + log.info(failmssg) + if verbose: + print(failmssg) + return False + + # Then check that elements use the same channels + specchans = [] + for elem in self._data.values(): + chans = _channelListSorter(elem.channels) + specchans.append(chans) + if specchans == []: # case of empty Sequence + chans = None + specchans = [None] + if specchans.count(chans) != len(specchans): + failmssg = ( + "checkConsistency failed: different elements specify different channels" + ) + log.info(failmssg) + if verbose: + print(failmssg) + return False + + # TODO: must all elements have same length? Does any AWG require this? + + # Finally, check that all positions are filled + positions = list(self._data.keys()) + if positions == []: # case of empty Sequence + positions = [1] + if not positions == list(range(1, len(positions) + 1)): + failmssg = ( + "checkConsistency failed: inconsistent sequence" + "positions. Must be 1, 2, 3, ..." + ) + log.info(failmssg) + if verbose: + print(failmssg) + return False + + # If all three tests pass... + return True
+ + + @property + def description(self): + """ + Return a dictionary fully describing the Sequence. + """ + desc = {} + + for pos, elem in self._data.items(): + desc[str(pos)] = {} + desc[str(pos)]["channels"] = elem.description + try: + sequencing = self._sequencing[pos] + seqdict = { + "Wait trigger": sequencing["twait"], + "Repeat": sequencing["nrep"], + "jump_input": sequencing["jump_input"], + "jump_target": sequencing["jump_target"], + "Go to": sequencing["goto"], + } + desc[str(pos)]["sequencing"] = seqdict + except KeyError: + desc[str(pos)]["sequencing"] = "Not set" + desc["awgspecs"] = self._awgspecs + return desc + +
+[docs] + def write_to_json(self, path_to_file: str) -> None: + """ + Writes sequences to JSON file + + Args: + path_to_file: the path to the file to write to ex: + path_to_file/sequense.json + """ + with open(path_to_file, "w") as fp: + json.dump(self.description, fp, indent=4)
+ + +
+[docs] + @classmethod + def sequence_from_description(cls, seq_dict: dict) -> "Sequence": + """ + Returns a sequence from a description given as a dict + + Args: + seq_dict: a dict in the same form as returned by + Sequence.description + """ + + awgspecs = seq_dict["awgspecs"] + SR = awgspecs["SR"] + elem_list = list(seq_dict.keys()) + new_instance = cls() + + for ele in elem_list[:-1]: + channels_list = list(seq_dict[ele]["channels"].keys()) + elem = Element() + for chan in channels_list: + bp_sum = BluePrint.blueprint_from_description( + seq_dict[ele]["channels"][chan] + ) + bp_sum.setSR(SR) + elem.addBluePrint(int(chan), bp_sum) + if "flags" in seq_dict[ele]["channels"][chan]: + flags = seq_dict[ele]["channels"][chan]["flags"] + elem.addFlags(int(chan), flags) + ChannelAmplitude = awgspecs[f"channel{chan}_amplitude"] + new_instance.setChannelAmplitude( + int(chan), ChannelAmplitude + ) # Call signature: channel, amplitude (peak-to-peak) + ChannelOffset = awgspecs[f"channel{chan}_offset"] + new_instance.setChannelOffset(int(chan), ChannelOffset) + + new_instance.addElement(int(ele), elem) + sequencedict = seq_dict[ele]["sequencing"] + new_instance.setSequencingTriggerWait( + int(ele), sequencedict["Wait trigger"] + ) + new_instance.setSequencingNumberOfRepetitions( + int(ele), sequencedict["Repeat"] + ) + new_instance.setSequencingEventInput(int(ele), sequencedict["jump_input"]) + new_instance.setSequencingEventJumpTarget( + int(ele), sequencedict["jump_target"] + ) + new_instance.setSequencingGoto(int(ele), sequencedict["Go to"]) + new_instance.setSR(SR) + return new_instance
+ + +
+[docs] + @classmethod + def init_from_json(cls, path_to_file: str) -> "Sequence": + """ + Reads sequense from JSON file + + Args: + path_to_file: the path to the file to be read ex: + path_to_file/sequense.json + This function is the inverse of write_to_json + The JSON file needs to be structured as if it was writen + by the function write_to_json + """ + new_instance = cls() + with open(path_to_file) as fp: + data_loaded = json.load(fp) + + new_instance = Sequence.sequence_from_description(data_loaded) + return new_instance
+ + + @property + def name(self): + return self._name + + @name.setter + def name(self, newname): + if not isinstance(newname, str): + raise ValueError("The sequence name must be a string") + self._name = newname + + @property + def length_sequenceelements(self): + """ + Returns the current number of specified sequence elements + """ + return len(self._data) + + @property + def SR(self): + """ + Returns the sample rate, if defined. Else returns -1. + """ + try: + SR = self._awgspecs["SR"] + except KeyError: + SR = -1 + + return SR + + @property + def channels(self): + """ + Returns a list of the specified channels of the sequence + """ + if self.checkConsistency(): + return self.element(1).channels + else: + raise SequenceConsistencyError( + "Sequence not consistent. Can not figure out the channels." + ) + + @property + def points(self): + """ + Returns the number of points of the sequence, disregarding + sequencing info (like repetitions). Useful for asserting upload + times, i.e. the size of the built sequence. + """ + total = 0 + for elem in self._data.values(): + total += elem.points + return total + + @property + def duration(self) -> float: + """ + Returns the duration in seconds of the sequence. + """ + duration = 0.0 + for pos, elem in self._data.items(): + nrep = self._sequencing[pos]["nrep"] + duration += nrep * elem.duration + return duration + +
+[docs] + def element(self, pos): + """ + Returns the element at the given position. Changes made to the return + value of this methods will apply to the sequence. If this is undesired, + make a copy of the returned element using Element.copy + + Args: + pos (int): The sequence position + + Raises: + KeyError: If no element is specified at the given position + """ + try: + elem = self._data[pos] + except KeyError: + raise KeyError(f"No element specified at sequence position {pos}") + + return elem
+ + + @staticmethod + def _plotSummary(seq: dict[int, dict]) -> dict[int, dict[str, np.ndarray]]: + """ + Return a plotting summary of a subsequence. + + Args: + seq: The 'content' value of a forged sequence where a + subsequence resides + + Returns: + A dict that looks like a forged element, but all waveforms + are just two points, np.array([min, max]) + """ + + output = {} + + # we assume correctness, all postions specify the same channels + chans = seq[1]["data"].keys() + + minmax = dict(zip(chans, [(0, 0)] * len(chans))) + + for element in seq.values(): + arr_dict = element["data"] + + for chan in chans: + wfm = arr_dict[chan]["wfm"] + if wfm.min() < minmax[chan][0]: + minmax[chan] = (wfm.min(), minmax[chan][1]) + if wfm.max() > minmax[chan][1]: + minmax[chan] = (minmax[chan][0], wfm.max()) + output[chan] = { + "wfm": np.array(minmax[chan]), + "m1": np.zeros(2), + "m2": np.zeros(2), + "time": np.linspace(0, 1, 2), + } + + return output + +
+[docs] + def forge( + self, + apply_delays: bool = True, + apply_filters: bool = True, + includetime: bool = False, + ) -> dict[int, dict]: + """ + Forge the sequence, applying all specified transformations + (delays and ripasso filter corrections). Copies the data, so + that the sequence is not modified by forging. + + Args: + apply_delays: Whether to apply the assigned channel delays + (if any) + apply_filters: Whether to apply the assigned channel filters + (if any) + includetime: Whether to include the time axis and the segment + durations (a list) with the arrays. Used for plotting. + + Returns: + A nested dictionary holding the forged sequence. + """ + # Validation + if not self.checkConsistency(): + raise ValueError( + "Can not generate output. Something is " + "inconsistent. Please run " + "checkConsistency(verbose=True) for more details" + ) + + output: dict[int, dict] = {} + channels = self.channels + data = deepcopy(self._data) + seqlen = len(data.keys()) + + # TODO: in this function, we iterate through the sequence three times + # It is probably worth considering refactoring that into a single + # iteration, although that may compromise readability + + # Apply channel delays. + + if apply_delays: + delays = [] + for chan in channels: + try: + delays.append(self._awgspecs[f"channel{chan}_delay"]) + except KeyError: + delays.append(0) + + for pos in range(1, seqlen + 1): + if isinstance(data[pos], Sequence): + subseq = data[pos] + for elem in subseq._data.values(): + elem._applyDelays(delays) + elif isinstance(data[pos], Element): + data[pos]._applyDelays(delays) + + # forge arrays and form the output dict + for pos in range(1, seqlen + 1): + output[pos] = {} + output[pos]["sequencing"] = self._sequencing[pos] + if isinstance(data[pos], Sequence): + subseq = data[pos] + output[pos]["type"] = "subsequence" + output[pos]["content"] = {} + for pos2 in range(1, subseq.length_sequenceelements + 1): + output[pos]["content"][pos2] = {"data": {}, "sequencing": {}} + elem = subseq.element(pos2) + dictdata = elem.getArrays(includetime=includetime) + output[pos]["content"][pos2]["data"] = dictdata + seqing = subseq._sequencing[pos2] + output[pos]["content"][pos2]["sequencing"] = seqing + # TODO: update sequencing + elif isinstance(data[pos], Element): + elem = data[pos] + output[pos]["type"] = "element" + dictdata = elem.getArrays(includetime=includetime) + output[pos]["content"] = {1: {"data": dictdata}} + + # apply filter corrections to forged arrays + if apply_filters: + for pos1 in range(1, seqlen + 1): + thiselem = output[pos1]["content"] + for pos2 in thiselem.keys(): + data = thiselem[pos2]["data"] + for channame in data.keys(): + keystr = f"channel{channame}_filtercompensation" + if keystr in self._awgspecs.keys(): + kind = self._awgspecs[keystr]["kind"] + order = self._awgspecs[keystr]["order"] + f_cut = self._awgspecs[keystr]["f_cut"] + tau = self._awgspecs[keystr]["tau"] + if f_cut is None: + f_cut = 1 / tau + prefilter = data[channame]["wfm"] + postfilter = applyInverseRCFilter( + prefilter, self.SR, kind, f_cut, order, DCgain=1 + ) + ( + output[pos1]["content"][pos2]["data"][channame]["wfm"] + ) = postfilter + + return output
+ + + def _prepareForOutputting(self) -> list[dict[int, Any]]: + """ + The preparser for numerical output. Applies delay and ripasso + corrections. + + Returns: + A list of outputs of the Element's getArrays functions, i.e. + a list of dictionaries with key position (int) and value + an np.ndarray of array([wfm, m1, m2, time]), where the + wfm values are still in V. The particular backend output + function must rescale to the specific format it adheres to. + """ + # Validation + if not self.checkConsistency(): + raise ValueError( + "Can not generate output. Something is " + "inconsistent. Please run " + "checkConsistency(verbose=True) for more details" + ) + # + # + channels = self.element(1).channels # all elements have ident. chans + # We copy the data so that the state of the Sequence is left unaltered + # by outputting for AWG + data = deepcopy(self._data) + seqlen = len(data.keys()) + # check if sequencing information is specified for each element + if not sorted(list(self._sequencing.keys())) == list(range(1, seqlen + 1)): + raise ValueError( + "Can not generate output for file; incorrect sequencer information." + ) + + # Verify physical amplitude specifiations + for chan in channels: + ampkey = f"channel{chan}_amplitude" + if ampkey not in self._awgspecs.keys(): + raise KeyError( + "No amplitude specified for channel {chan}. Can not continue." + ) + + # Apply channel delays. + delays = [] + for chan in channels: + try: + delays.append(self._awgspecs[f"channel{chan}_delay"]) + except KeyError: + delays.append(0) + maxdelay = max(delays) + + for pos in range(1, seqlen + 1): + for chanind, chan in enumerate(channels): + element = data[pos] + delay = delays[chanind] + + if "blueprint" in element._data[chan].keys(): + blueprint = element._data[chan]["blueprint"] + # prevent information about flags to be lost + if "flags" in element._data[chan].keys(): + flags = element._data[chan]["flags"] + else: + flags = None + + # update existing waituntils + for segpos in range(len(blueprint._funlist)): + if blueprint._funlist[segpos] == "waituntil": + oldwait = blueprint._argslist[segpos][0] + blueprint._argslist[segpos] = (oldwait + delay,) + # insert delay before the waveform + if delay > 0: + blueprint.insertSegment(0, "waituntil", (delay,), "waituntil") + # add zeros at the end + if maxdelay - delay > 0: + blueprint.insertSegment( + -1, PulseAtoms.ramp, (0, 0), dur=maxdelay - delay + ) + # TODO: is the next line even needed? + # If not, remove the code updating the flags below + # and the one remembering them above + element.addBluePrint(chan, blueprint) + if flags is not None: + element.addFlags(chan, flags) + + else: + arrays = element._data[chan]["array"] + for name, arr in arrays.items(): + pre_wait = np.zeros(int(delay / self.SR)) + post_wait = np.zeros(int((maxdelay - delay) / self.SR)) + arrays[name] = np.concatenate((pre_wait, arr, post_wait)) + + # Now forge all the elements as specified + elements = [] # the forged elements + for pos in range(1, seqlen + 1): + elements.append(data[pos].getArrays()) + + # Now that the numerical arrays exist, we can apply filter compensation + for chan in channels: + keystr = f"channel{chan}_filtercompensation" + if keystr in self._awgspecs.keys(): + kind = self._awgspecs[keystr]["kind"] + order = self._awgspecs[keystr]["order"] + f_cut = self._awgspecs[keystr]["f_cut"] + tau = self._awgspecs[keystr]["tau"] + if f_cut is None: + f_cut = 1 / tau + for pos in range(seqlen): + prefilter = elements[pos][chan]["wfm"] + postfilter = applyInverseRCFilter( + prefilter, self.SR, kind, f_cut, order, DCgain=1 + ) + elements[pos][chan]["wfm"] = postfilter + + return elements + +
+[docs] + def outputForSEQXFile( + self, + ) -> tuple[ + list[int], + list[int], + list[int], + list[int], + list[int], + list[list[np.ndarray]], + list[float], + str, + ]: + """ + Generate a tuple matching the call signature of the QCoDeS + AWG70000A driver's `makeSEQXFile` function. If channel delays + have been specified, they are added to the ouput before exporting. + The intended use of this function together with the QCoDeS driver is + + .. code:: python + + pkg = seq.outputForSEQXFile() + seqx = awg70000A.makeSEQXFile(*pkg) + + Returns: + A tuple holding (trig_waits, nreps, event_jumps, event_jump_to, + go_to, wfms, amplitudes, seqname) + """ + + # most of the footwork is done by the following function + elements = self._prepareForOutputting() + # _prepareForOutputting asserts that channel amplitudes and + # full sequencing is specified + seqlen = len(elements) + # all elements have ident. chans since _prepareForOutputting + # did not raise an exception + channels = self.element(1).channels + + for chan in channels: + offkey = f"channel{chan}_offset" + if offkey in self._awgspecs.keys(): + log.warning( + "Found a specified offset for channel " + f"{chan}, but .seqx files can't contain offset " + "information. Will ignore the offset." + "" + ) + + # now check that the amplitudes are within the allowed limits + # also verify that all waveforms are at least 2400 points + # No rescaling because the driver's _makeWFMXBinaryData does + # the rescaling + + amplitudes = [] + for chan in channels: + ampl = self._awgspecs[f"channel{chan}_amplitude"] + amplitudes.append(ampl) + if len(amplitudes) == 1: + amplitudes.append(0) + + for pos in range(1, seqlen + 1): + element = elements[pos - 1] + for chan in channels: + ampl = self._awgspecs[f"channel{chan}_amplitude"] + wfm = element[chan]["wfm"] + # check the waveform length + if len(wfm) < 2400: + raise ValueError( + "Waveform too short on channel " + f"{chan} at step {pos}; only {len(wfm)} points. " + "The required minimum is 2400 points." + "" + ) + # check whether the waveform voltages can be realised + if wfm.max() > ampl / 2: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}." + f" {wfm.max()} > {ampl / 2}!" + ) + if wfm.min() < -ampl / 2: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}. " + f"{wfm.min()} < {-ampl / 2}!" + ) + element[chan]["wfm"] = wfm + elements[pos - 1] = element + + # Finally cast the lists into the shapes required by the AWG driver + + waveforms = cast(list[list[np.ndarray]], [[] for dummy in range(len(channels))]) + nreps = [] + trig_waits = [] + gotos = [] + jump_states = [] + jump_tos = [] + + # Since sequencing options are valid/invalid differently for + # different backends, we make the validation here + for pos in range(1, seqlen + 1): + for chanind, chan in enumerate(channels): + wfm = elements[pos - 1][chan]["wfm"] + m1 = elements[pos - 1][chan]["m1"] + m2 = elements[pos - 1][chan]["m2"] + waveforms[chanind].append(np.array([wfm, m1, m2])) + + twait = self._sequencing[pos]["twait"] + nrep = self._sequencing[pos]["nrep"] + jump_to = self._sequencing[pos]["jump_target"] + jump_state = self._sequencing[pos]["jump_input"] + goto = self._sequencing[pos]["goto"] + + if twait not in [0, 1, 2, 3]: + raise SequencingError( + "Invalid trigger input at position" + f"{pos}: {twait}. Must be 0, 1, 2, or 3." + "" + ) + + if jump_state not in [0, 1, 2, 3]: + raise SequencingError( + "Invalid event jump input at position" + f"{pos}: {twait}. Must be either 0, 1, 2, or 3." + "" + ) + + if nrep not in range(0, 16384): + raise SequencingError( + "Invalid number of repetions at position" + f"{pos}: {nrep}. Must be either 0 (infinite) " + "or 1-16,383." + ) + + if jump_to not in range(-1, seqlen + 1): + raise SequencingError( + "Invalid event jump target at position" + f"{pos}: {jump_to}. Must be either -1 (next)," + f" 0 (off), or 1-{seqlen}." + "" + ) + + if goto not in range(0, seqlen + 1): + raise SequencingError( + "Invalid goto target at position" + f"{pos}: {goto}. Must be either 0 (next)," + f" or 1-{seqlen}." + "" + ) + + trig_waits.append(twait) + nreps.append(nrep) + jump_tos.append(jump_to) + jump_states.append(jump_state) + gotos.append(goto) + + return ( + trig_waits, + nreps, + jump_states, + jump_tos, + gotos, + waveforms, + amplitudes, + self.name, + )
+ + +
+[docs] + def outputForSEQXFileWithFlags( + self, + ) -> tuple[ + list[int], + list[int], + list[int], + list[int], + list[int], + list[list[np.ndarray]], + list[float], + str, + list[list[list[int]]], + ]: + """ + Generate a tuple matching the call signature of the QCoDeS + AWG70000A driver's `makeSEQXFile` function. Same as outputForSEQXFile(), + but also includes information about the flags. + + Returns: + A tuple holding (trig_waits, nreps, event_jumps, event_jump_to, + go_to, wfms, amplitudes, seqname, flags) + """ + + elements = self._prepareForOutputting() + seqlen = len(elements) + channels = self.element(1).channels + + # add flags for every element and channel + all_flags = [] + for chanind, chan in enumerate(channels): + flags_pos = [] + for pos in range(1, seqlen + 1): + if "flags" in elements[pos - 1][chan]: + flags = elements[pos - 1][chan]["flags"].tolist() + else: + flags = [0, 0, 0, 0] + flags_pos.append(flags) + all_flags.append(flags_pos) + + return self.outputForSEQXFile() + (all_flags,)
+ + +
+[docs] + def outputForAWGFile(self): + """ + Returns a sliceable object with items matching the call + signature of the 'make_*_awg_file' functions of the QCoDeS + AWG5014 driver. One may then construct an awg file as follows + (assuming that seq is the sequence object): + + .. code:: python + + package = seq.outputForAWGFile() + make_awg_file(*package[:], **kwargs) + + + """ + + elements = self._prepareForOutputting() + seqlen = len(elements) + # all elements have ident. chans since _prepareForOutputting + # did not raise an exception + channels = self.element(1).channels + + for chan in channels: + offkey = f"channel{chan}_offset" + if offkey not in self._awgspecs.keys(): + raise ValueError( + f"No specified offset for channel {chan}, can not continue." + ) + + # Apply channel scaling + # We must rescale to the interval -1, 1 where 1 is ampl/2+off and -1 is + # -ampl/2+off. + # + def rescaler(val, ampl, off): + return val / ampl * 2 - off + + for pos in range(1, seqlen + 1): + element = elements[pos - 1] + for chan in channels: + ampl = self._awgspecs[f"channel{chan}_amplitude"] + off = self._awgspecs[f"channel{chan}_offset"] + wfm = element[chan]["wfm"] + # check whether the waveform voltages can be realised + if wfm.max() > ampl / 2 + off: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}." + f" {wfm.max()} > {ampl / 2 + off}!" + ) + if wfm.min() < -ampl / 2 + off: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}. " + f"{wfm.min()} < {-ampl / 2 + off}!" + ) + wfm = rescaler(wfm, ampl, off) + element[chan]["wfm"] = wfm + elements[pos - 1] = element + + # Finally cast the lists into the shapes required by the AWG driver + waveforms = [[] for dummy in range(len(channels))] + m1s = [[] for dummy in range(len(channels))] + m2s = [[] for dummy in range(len(channels))] + nreps = [] + trig_waits = [] + gotos = [] + jump_tos = [] + + # Since sequencing options are valid/invalid differently for + # different backends, we make the validation here + for pos in range(1, seqlen + 1): + for chanind, chan in enumerate(channels): + waveforms[chanind].append(elements[pos - 1][chan]["wfm"]) + m1s[chanind].append(elements[pos - 1][chan]["m1"]) + m2s[chanind].append(elements[pos - 1][chan]["m2"]) + + twait = self._sequencing[pos]["twait"] + nrep = self._sequencing[pos]["nrep"] + jump_to = self._sequencing[pos]["jump_target"] + goto = self._sequencing[pos]["goto"] + + if twait not in [0, 1]: + raise SequencingError( + "Invalid trigger wait state at position" + f"{pos}: {twait}. Must be either 0 or 1." + "" + ) + + if nrep not in range(0, 65537): + raise SequencingError( + "Invalid number of repetions at position" + f"{pos}: {nrep}. Must be either 0 (infinite) " + "or 1-65,536." + ) + + if jump_to not in range(-1, seqlen + 1): + raise SequencingError( + "Invalid event jump target at position" + f"{pos}: {jump_to}. Must be either -1 (next)," + f" 0 (off), or 1-{seqlen}." + "" + ) + + if goto not in range(0, seqlen + 1): + raise SequencingError( + "Invalid goto target at position" + f"{pos}: {goto}. Must be either 0 (next)," + f" or 1-{seqlen}." + "" + ) + + trig_waits.append(twait) + nreps.append(nrep) + jump_tos.append(jump_to) + gotos.append(goto) + + # ...and make a sliceable object out of them + output = _AWGOutput( + (waveforms, m1s, m2s, nreps, trig_waits, gotos, jump_tos), self.channels + ) + + return output
+
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/tools.html b/_modules/broadbean/tools.html new file mode 100644 index 000000000..2d0de613c --- /dev/null +++ b/_modules/broadbean/tools.html @@ -0,0 +1,481 @@ + + + + + + + + broadbean.tools - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.tools

+# High-level tool for sequence building and manipulation
+#
+
+import logging
+
+import numpy as np
+
+from broadbean.sequence import Sequence, SequenceConsistencyError
+
+log = logging.getLogger(__name__)
+
+
+
+[docs] +def makeLinearlyVaryingSequence(baseelement, channel, name, arg, start, stop, step): + """ + Make a pulse sequence where a single parameter varies linearly. + The pulse sequence will consist of N copies of the same element with just + the specified argument changed (N = abs(stop-start)/steps) + + Args: + baseelement (Element): The basic element. + channel (int): The channel where the change should happen + name (str): Name of the blueprint segment to change + arg (Union[str, int]): Name (str) or position (int) of the argument + to change. If the arg is 'duration', the duration is changed + instead. + start (float): Start point of the variation (included) + stop (float): Stop point of the variation (included) + step (float): Increment of the variation + """ + + # TODO: validation + # TODO: Make more general varyer and refactor code + + sequence = Sequence() + + sequence.setSR(baseelement.SR) + + iterator = np.linspace(start, stop, round(abs(stop - start) / step) + 1) + + for ind, val in enumerate(iterator): + element = baseelement.copy() + if arg == "duration": + element.changeDuration(channel, name, val) + else: + element.changeArg(channel, name, arg, val) + sequence.addElement(ind + 1, element) + + return sequence
+ + + +
+[docs] +def makeVaryingSequence(baseelement, channels, names, args, iters): + """ + Make a pulse sequence where N parameters vary simultaneously in M steps. + The user inputs a baseelement which is copied M times and changed + according to the given inputs. + + Args: + baseelement (Element): The basic element. + channels (Union[list, tuple]): Either a list or a tuple of channels on + which to find the blueprint to change. Must have length N. + names (Union[list, tuple]): Either a list or a tuple of names of the + segment to change. Must have length N. + args (Union[list, tuple]): Either a list or a tuple of argument + specifications for the argument to change. Use 'duration' to change + the segment duration. Must have length N. + iters (Union[list, tuple]): Either a list or a tuple of length N + containing Union[list, tuple, range] of length M. + + Raises: + ValueError: If not channels, names, args, and iters are of the same + length. + ValueError: If not each iter in iters specifies the same number of + values. + """ + + # Validation + baseelement.validateDurations() + + inputlengths = [len(channels), len(names), len(args), len(iters)] + if not inputlengths.count(inputlengths[0]) == len(inputlengths): + raise ValueError( + "Inconsistent number of channel, names, args, and " + "parameter sequences. Please specify the same number " + "of each." + ) + noofvals = [len(itr) for itr in iters] + if not noofvals.count(noofvals[0]) == len(iters): + raise ValueError( + "Not the same number of values in each parameter " + "value sequence (input argument: iters)" + ) + + sequence = Sequence() + sequence.setSR(baseelement.SR) + + for elnum in range(1, noofvals[0] + 1): + sequence.addElement(elnum, baseelement.copy()) + + for chan, name, arg, vals in zip(channels, names, args, iters): + for mpos, val in enumerate(vals): + element = sequence.element(mpos + 1) + if arg == "duration": + element.changeDuration(chan, name, val) + else: + element.changeArg(chan, name, arg, val) + + log.info("Created varying sequence using makeVaryingSequence. Now validating it...") + + if not sequence.checkConsistency(): + raise SequenceConsistencyError("Invalid sequence. See log for details.") + else: + log.info("Valid sequence") + return sequence
+ + + +
+[docs] +def repeatAndVarySequence(seq, poss, channels, names, args, iters): + """ + Repeat a sequence and vary part(s) of it. Returns a new sequence. + Given N specifications of M steps, N parameters are varied in M + steps. + + Args: + seq (Sequence): The sequence to be repeated. + poss (Union[list, tuple]): A length N list/tuple specifying at which + sequence position(s) the blueprint to change is. + channels (Union[list, tuple]): A length N list/tuple specifying on + which channel(s) the blueprint to change is. + names (Union[list, tuple]): A length N list/tuple specifying the name + of the segment to change. + args (Union[list, tuple]): A length N list/tuple specifying which + argument to change. A valid argument is also 'duration'. + iters (Union[list, tuple]): A length N list/tuple containing length + M indexable iterables with the values to step through. + """ + + if not seq.checkConsistency(): + raise SequenceConsistencyError( + "Inconsistent input sequence! Can not " + "proceed. Check all positions " + "and channels." + ) + + inputlens = [len(poss), len(channels), len(names), len(args), len(iters)] + if not inputlens.count(inputlens[0]) == len(inputlens): + raise ValueError( + "Inconsistent number of position, channel, name, args" + ", and " + "parameter sequences. Please specify the same number " + "of each." + ) + noofvals = [len(itr) for itr in iters] + if not noofvals.count(noofvals[0]) == len(iters): + raise ValueError( + "Not the same number of values in each parameter " + "value sequence (input argument: iters)" + ) + + newseq = Sequence() + newseq._awgspecs = seq._awgspecs + + no_of_steps = noofvals[0] + + for step in range(no_of_steps): + tempseq = seq.copy() + for pos, chan, name, arg, vals in zip(poss, channels, names, args, iters): + element = tempseq.element(pos) + val = vals[step] + + if arg == "duration": + element.changeDuration(chan, name, val) + else: + element.changeArg(chan, name, arg, val) + newseq = newseq + tempseq + + return newseq
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 000000000..7a22d0ca3 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,304 @@ + + + + + + + + Overview: module code - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+ +
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_sources/api/generated/broadbean.rst.txt b/_sources/api/generated/broadbean.rst.txt new file mode 100644 index 000000000..396083cd7 --- /dev/null +++ b/_sources/api/generated/broadbean.rst.txt @@ -0,0 +1,69 @@ +broadbean package +================= + +Submodules +---------- + +broadbean.blueprint module +-------------------------- + +.. automodule:: broadbean.blueprint + :members: + :undoc-members: + :show-inheritance: + +broadbean.broadbean module +-------------------------- + +.. automodule:: broadbean.broadbean + :members: + :undoc-members: + :show-inheritance: + +broadbean.element module +------------------------ + +.. automodule:: broadbean.element + :members: + :undoc-members: + :show-inheritance: + +broadbean.plotting module +------------------------- + +.. automodule:: broadbean.plotting + :members: + :undoc-members: + :show-inheritance: + +broadbean.ripasso module +------------------------ + +.. automodule:: broadbean.ripasso + :members: + :undoc-members: + :show-inheritance: + +broadbean.sequence module +------------------------- + +.. automodule:: broadbean.sequence + :members: + :undoc-members: + :show-inheritance: + +broadbean.tools module +---------------------- + +.. automodule:: broadbean.tools + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: broadbean + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/changes/0.10.0.rst.txt b/_sources/changes/0.10.0.rst.txt new file mode 100644 index 000000000..b6791eaa9 --- /dev/null +++ b/_sources/changes/0.10.0.rst.txt @@ -0,0 +1,18 @@ +Changelog for broadbean 0.10.0 +============================== + +The January 2021 release of broadbean. + + +Breaking Changes: +_________________ + +There are no breaking changes in this release of broadbean + + +New: +____ + +- Support for reading and writing broadbean Sequences, elements and bluprints to and from a json file +- made broadbean compatiple with numpy version 1.18 +- Include LICENSE in package diff --git a/_sources/changes/0.11.0.rst.txt b/_sources/changes/0.11.0.rst.txt new file mode 100644 index 000000000..28b7e745f --- /dev/null +++ b/_sources/changes/0.11.0.rst.txt @@ -0,0 +1,38 @@ +Changelog for broadbean 0.11.0 +============================== + +The August 2022 release of broadbean with many great improvements for modernizing broadbean's infrastructure. + + +Breaking Changes: +_________________ + +- Methods and functions marked for deletion in version `0.10.0` have now been removed. Specifically + `BluePrint.plot`, `broadbean.bluePrintPlotter`, `Element.plotElement`, `Sequence.plotSequence` + and `Sequence.plotAWGOutput`. (#107) + +New: +____ + +- New `Sequence` method `outputForSEQXFileWithFlags` for setting flags for every element forming a sequence. + This is useful for auxiliary outputs on a Tektronix AWG70000. (#101) + +Improved: +_________ + +- Fix for invalid identity comparisons in blueprint submodule. (#102) + + +Behind The Scenes: +__________________ + +- Updated `README.md` that includes updated link to `broadbean` documentation. (#158) +- Replace Conda test job with regular pip job on windows. (#139) +- Enable dependabot for `broadbean`. (#111) +- Enable precommit hook. (#110) +- Documentation infrastructure improvements. (#110) +- Modernize setup and build infrastructure (convert to pep516/517, build wheels and sdist using build, + automatic upload to pypi, move config to pyproject.toml and setup.cfg and pinning dependencies with + requirements.txt). (#109) +- Move tests into package to include them into distribution. (#108) +- Use GitHub actions, test on python 3.7-3.10, remove python 3.6 support, remove Travis and AppVeyor. (#103) diff --git a/_sources/changes/index.rst.txt b/_sources/changes/index.rst.txt new file mode 100644 index 000000000..dd308f203 --- /dev/null +++ b/_sources/changes/index.rst.txt @@ -0,0 +1,6 @@ +Changelogs +========== + +.. toctree:: + 0.11.0 <0.11.0> + 0.10.0 <0.10.0> diff --git a/_sources/examples/Example_Write_Read_JSON.ipynb.txt b/_sources/examples/Example_Write_Read_JSON.ipynb.txt new file mode 100644 index 000000000..3020a1ecf --- /dev/null +++ b/_sources/examples/Example_Write_Read_JSON.ipynb.txt @@ -0,0 +1,7096 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Read and Write from JSON file Tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [], + "source": [ + "#\n", + "# IMPORTS\n", + "#\n", + "%matplotlib nbagg\n", + "import matplotlib as mpl\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write BluePrint from JSON file Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Building a BluePrint" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "plotter(bp_boxes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the BluePrint to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the BluePrint back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_boxes_read = bb.BluePrint.init_from_json(\"blue.json\")\n", + "bp_boxes_read.setSR(\n", + " 1e9\n", + ") # note the blueprint readback do not have a sample rate attached\n", + "plotter(bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the BluePrints are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(bp_boxes == bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Element from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the Element to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "elem1.write_to_json(\"elem.json\")\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the Element back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [], + "source": [ + "elem_read = bb.Element.init_from_json(\"elem.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the Element.description are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(\n", + " elem1.description == elem_read.description\n", + ") ## note that the elements are not identical only the discription" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Sequence from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "#\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(3, bp_boxes)\n", + "elem2.addBluePrint(1, bp_sineandboxes)\n", + "\n", + "# Fill up the sequence\n", + "seq1.addElement(1, elem1) # Call signature: seq. pos., element\n", + "seq1.addElement(2, elem2)\n", + "seq1.addElement(3, elem1)\n", + "\n", + "# set its sample rate\n", + "seq1.setSR(elem1.SR)\n", + "\n", + "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", + "seq1.setChannelOffset(1, 0)\n", + "seq1.setChannelAmplitude(3, 10e-3)\n", + "seq1.setChannelOffset(3, 0)\n", + "\n", + "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", + "seq1.setSequencingTriggerWait(1, 0)\n", + "seq1.setSequencingNumberOfRepetitions(1, 2)\n", + "seq1.setSequencingEventJumpTarget(1, 0)\n", + "seq1.setSequencingGoto(1, 2)\n", + "#\n", + "seq1.setSequencingTriggerWait(2, 0)\n", + "seq1.setSequencingNumberOfRepetitions(2, 2)\n", + "seq1.setSequencingEventJumpTarget(2, 0)\n", + "seq1.setSequencingGoto(2, 3)\n", + "#\n", + "seq1.setSequencingTriggerWait(3, 0)\n", + "seq1.setSequencingNumberOfRepetitions(3, 2)\n", + "seq1.setSequencingEventJumpTarget(3, 0)\n", + "seq1.setSequencingGoto(3, 1)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing the Sequence to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [], + "source": [ + "seq1.write_to_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reading the Sequence back from the file " + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": {}, + "outputs": [], + "source": [ + "seq = bb.Sequence.init_from_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(seq)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test if the Sequences are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(seq == seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Ekstras\n", + "Example of how read and write blueprint from/to Sequence json file if needed." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [], + "source": [ + "def readblueprint(path: str, element: int = 1, channel: int = 1):\n", + " tempseq = bb.Sequence.init_from_json(path)\n", + " return tempseq.element(element)._data[channel][\"blueprint\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [], + "source": [ + "def writeblueprint(\n", + " blueprint, path: str, SR: float = 1e9, SeqAmp: float = 10e-3, SeqOffset: float = 0\n", + "): # -> None\n", + " if blueprint.SR is None:\n", + " blueprint.setSR(SR)\n", + " elemtmp = bb.Element()\n", + " seqtmp = bb.Sequence()\n", + " elemtmp.addBluePrint(1, blueprint)\n", + " seqtmp.addElement(1, elemtmp)\n", + " seqtmp.setSR(blueprint.SR)\n", + " seqtmp.setChannelAmplitude(1, SeqAmp)\n", + " seqtmp.setChannelOffset(1, 0)\n", + " seqtmp.write_to_json(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": {}, + "outputs": [], + "source": [ + "writeblueprint(bp_boxes, \"boxes.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_one_two = readblueprint(\"testdata.json\", element=2, channel=1)\n", + "plotter(bp_one_two)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/examples/Filter_compensation.ipynb.txt b/_sources/examples/Filter_compensation.ipynb.txt new file mode 100644 index 000000000..31411502a --- /dev/null +++ b/_sources/examples/Filter_compensation.ipynb.txt @@ -0,0 +1,3483 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filter compensation with the ripasso module\n", + "\n", + "The broadbean module gets a helping hand from the ripasso module when it needs to compensate for transfer functions of transmission lines.\n", + "\n", + "The ripasso module lets us apply filters and their inverses to signals.\n", + "\n", + "The module has two built-in types of filter, namely an RC-circuit high-pass and an RC-circuit low-pass. The transfer functions are\n", + "\n", + "$$\n", + "H_n(f)=\\left(\\frac{2\\pi f\\mathrm{i}\\tau}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order high-pass and\n", + "$$\n", + "L_n(f)=\\left(\\frac{1}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order low-pass. The parameter $\\tau$ is one over the cut-off frequency.\n", + "\n", + "The inversion (filter compensation) is performed by mulitplying with the inverse transfer function in frequency space and transforming back to the time domain, e.g. for a given signal $s(t)$ and a high-pass filter of order n,\n", + "\n", + "$$\n", + " s_\\text{filtered}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_n(f)](t)\n", + "$$\n", + "and\n", + "$$\n", + " s_\\text{compensated}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_{-n}(f)](t).\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from broadbean.ripasso import (\n", + " applyCustomTransferFunction,\n", + " applyInverseRCFilter,\n", + " applyRCFilter,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def squarewave(npts, periods=5):\n", + " periods = int(periods)\n", + " array = np.zeros(npts)\n", + "\n", + " for n in range(periods):\n", + " array[int(n * npts / periods) : int((2 * n + 1) * npts / 2 / periods)] = 1\n", + "\n", + " return array" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## RC Filters" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's make some signals!\n", + "\n", + "SR = int(10e3)\n", + "npts = int(10e3)\n", + "\n", + "f_cut = 12 # filter cut-off frequency (Hz)\n", + "\n", + "time = np.linspace(0, npts / SR, npts)\n", + "T = time[-1]\n", + "\n", + "signal1 = np.sin(2 * np.pi * 5 / T * time) + 0.7 * np.cos(2 * np.pi * 1 / T * time)\n", + "signal2 = squarewave(npts, periods=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Then we may high-pass them or low-pass them\n", + "\n", + "filtertype = \"HP\"\n", + "# filtertype = 'LP'\n", + "\n", + "signal1_filt = applyRCFilter(signal1, SR, filtertype, f_cut, order=1)\n", + "signal2_filt = applyRCFilter(signal2, SR, filtertype, f_cut, order=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_comp, label=\"Compensated\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", + "axs[0].legend()\n", + "\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_comp, label=\"Compensated\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", + "axs[1].legend()\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Lets first make some semi-realistic transfer function\n", + "\n", + "tf_points = 500\n", + "\n", + "tf_freq = np.linspace(0, 1000, tf_points)\n", + "tf_amp = np.zeros(tf_points)\n", + "tf_amp += np.hanning(2 * tf_points)[tf_points:][::-1] # A high-pass\n", + "tf_amp += (\n", + " 0.1 # Note: a transfer function with very low values give unphysical compensation\n", + ")\n", + "tf_amp /= tf_amp.max()\n", + "\n", + "# and some experimental noise\n", + "tf_noise_amp = 0.02\n", + "tf_amp += (\n", + " np.convolve(0.02 * np.random.randn(tf_points), np.array([0.5, 1, 0.5]), mode=\"same\")\n", + " / 2\n", + ")\n", + "\n", + "# And then our favourite signal: the square wave\n", + "\n", + "sig_points = 1000\n", + "signal = squarewave(sig_points, periods=10) + 0.01 * np.random.randn(1000)\n", + "SR = 2000 # We pretend that the signal was sampled with this sample rate\n", + "time = np.linspace(0, sig_points / SR, sig_points)\n", + "\n", + "# Then we may apply the filter we made\n", + "\n", + "signal_filtered = applyCustomTransferFunction(signal, SR, tf_freq, tf_amp)\n", + "\n", + "# Or conversely, we may apply its inverse\n", + "\n", + "signal_compensated = applyCustomTransferFunction(\n", + " signal, SR, tf_freq, tf_amp, invert=True\n", + ")\n", + "\n", + "# Finally, it's nice to visualise things\n", + "\n", + "fig, axs = plt.subplots(2, 2)\n", + "axs[0, 0].plot(tf_freq, tf_amp)\n", + "axs[0, 0].set_xlabel(\"Freq. (Hz)\")\n", + "axs[0, 0].set_ylabel(\"Transfer func.\")\n", + "\n", + "axs[0, 1].plot(time, signal, color=\"#ff9900\")\n", + "axs[0, 1].set_xlabel(\"Time (s)\")\n", + "axs[0, 1].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[0, 1].set_title(\"Input\")\n", + "\n", + "axs[1, 0].plot(time, signal_filtered, color=\"#339966\")\n", + "axs[1, 0].set_xlabel(\"Time (s)\")\n", + "axs[1, 0].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[1, 0].set_title(\"Filtered\")\n", + "\n", + "axs[1, 1].plot(time, signal_compensated, color=\"#990033\")\n", + "axs[1, 1].set_xlabel(\"Time (s)\")\n", + "axs[1, 1].set_ylabel(\"Sig.ampl. (arb. un.)\")\n", + "axs[1, 1].set_title(\"Compensated\")\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/_sources/examples/Making_output_for_Tektronix_AWG70000A.ipynb.txt b/_sources/examples/Making_output_for_Tektronix_AWG70000A.ipynb.txt new file mode 100644 index 000000000..6fe524d0a --- /dev/null +++ b/_sources/examples/Making_output_for_Tektronix_AWG70000A.ipynb.txt @@ -0,0 +1,1075 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "The Tektronix AWG70000A series internally use `.seqx` files. The QCoDeS driver for the AWGs knows how to compile these files and broadbean knows what QCoDeS need to do so. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1: Make a sequence" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# We make a sequence consisting of a bsic element with:\n", + "# a wait time, a ramp up, a sine, a ramp down, and more wait time\n", + "# And then we vary the sine frequency\n", + "%matplotlib notebook\n", + "import numpy as np\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "sine = bb.PulseAtoms.sine\n", + "ramp = bb.PulseAtoms.ramp\n", + "\n", + "##########################\n", + "# settings\n", + "\n", + "SR = 25e9\n", + "t1 = 10e-9 # first wait time (s)\n", + "ramp_time = 5e-9 # the ramp time (s)\n", + "t_sine = 25e-9 # the sine time (s)\n", + "high_level = 0.1 # the high level (V)\n", + "sine_amp = 0.05 # the sine amplitude (V)\n", + "t2 = 150e-9 # the second wait time (s)\n", + "f0 = 1e9 # the base frequency of the sine (Hz)\n", + "\n", + "baseshape = bb.BluePrint()\n", + "baseshape.insertSegment(0, ramp, (0, 0), dur=t1)\n", + "baseshape.insertSegment(1, ramp, (0, high_level), dur=ramp_time)\n", + "baseshape.insertSegment(\n", + " 2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name=\"drive\"\n", + ")\n", + "baseshape.insertSegment(3, ramp, (high_level, 0), dur=ramp_time)\n", + "baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name=\"wait\")\n", + "baseshape.setSegmentMarker(\"wait\", (0, t_sine), 1)\n", + "baseshape.setSegmentMarker(\"drive\", (0, t_sine), 2)\n", + "baseshape.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Absolute time marker specification\n", + "\n", + "# The blueprint has a list of tuples for each marker. The tuples are (switch_on_time, duration)\n", + "\n", + "# create a blueprint\n", + "bp_atm = bb.BluePrint()\n", + "bp_atm.setSR(100)\n", + "bp_atm.insertSegment(0, ramp, (0, 1), dur=3)\n", + "bp_atm.insertSegment(1, sine, (0.5, 1, 1, 0), dur=2)\n", + "bp_atm.insertSegment(2, ramp, (1, 0), dur=3)\n", + "\n", + "# specify markers in absolute time\n", + "bp_atm.marker1 = [(1, 0.5), (2, 0.5)]\n", + "bp_atm.marker2 = [(1.5, 0.2), (2.5, 0.1)]\n", + "\n", + "plotter(bp_atm)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Relative time marker specification\n", + "\n", + "bp_rtm = bb.BluePrint()\n", + "bp_rtm.setSR(100)\n", + "bp_rtm.insertSegment(0, ramp, (0, 1), dur=1)\n", + "bp_rtm.insertSegment(1, ramp, (1, 1), dur=1)\n", + "bp_rtm.insertSegment(\n", + " 2, sine, (1.675, 1, 0, np.pi / 2), dur=1.5, name=\"mysine\"\n", + ") # This is the important segment\n", + "# make marker 1 go ON a bit before the sine comes on\n", + "bp_rtm.setSegmentMarker(\n", + " \"mysine\", (-0.1, 0.2), 1\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "bp_rtm.setSegmentMarker(\"mysine\", (0.75, 0.1), 2)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "# Even if we insert segments before and after the sine, the markers \"stick\" to the sine segment\n", + "bp_rtm.insertSegment(0, ramp, (0, 0), dur=1)\n", + "bp_rtm.insertSegment(-1, ramp, (0, 0.2), dur=1)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "\n", + "# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn\n", + "# markers ON. It is up to the user to ensure that markers switch off again as expected, i.e. that different marker\n", + "# specifications do not overlap." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modifying blueprints\n", + "([back to ToC](#Table-of-Contents))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=0.1e-6)\n", + "bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name=\"top\", dur=0.1e-6)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=0.1e-6)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 25e-3, 0, 0), dur=0.3e-6)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "# Now we create an element and add the blueprints to channel 1 and 3, respectively\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "\n", + "# We can check the validity of the element\n", + "elem1.validateDurations() # raises an ElementDurationError if something is wrong. If all is OK, does nothing.\n", + "\n", + "# And we can plot the element\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Designated channels: [1, 3]\n", + "Total duration: 6e-07 s\n", + "Sample rate: 1000000000.0 (Sa/S)\n" + ] + } + ], + "source": [ + "# An element has several features\n", + "print(f\"Designated channels: {elem1.channels}\")\n", + "print(f\"Total duration: {elem1.duration} s\")\n", + "print(f\"Sample rate: {elem1.SR} (Sa/S)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The sequence can be validated\n", + "seq1.checkConsistency() # returns True if all is well, raises errors if not\n", + "\n", + "# And the sequence can (if valid) be plotted\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tektronix AWG 5014 output\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The sequence object can output a tuple matching the call signature of the QCoDeS Tektronix AWG 5014 driver.\n", + "\n", + "For the translation from voltage to AWG unsigned integer format to be correct, the voltage ranges and offsets of the AWG channels must be specified (NB: This will **NOT** work if the channels on the AWG are in high/low mode).\n", + "\n", + "Furthermore, the AWG sequencer options should be specified for each sequence element." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For sanity checking, it may be helpful to see how the compensated waveforms look.\n", + "# The plotter function can display the delays and filter compensations\n", + "\n", + "plotter(seq1, apply_filters=True, apply_delays=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sequences varying parameters\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The module contains a few wrapper functions to easily generate sequences with some parameter(s) varying throughout the sequence. First two examples where a Base element is provided and varied, then an example where an existing Sequence is repeated." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1: vary the duration of a square pulse\n", + "\n", + "# First, we make a basic element, in this example containing just a single blueprint\n", + "basebp = bb.BluePrint()\n", + "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", + "basebp.setSR(100)\n", + "\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, basebp)\n", + "\n", + "plotter(baseelem)\n", + "\n", + "# Now we make a 5-step sequence varying the duration of the high level\n", + "# The inputs are lists, since several things can be varied (see Example 2)\n", + "channels = [1]\n", + "names = [\"varyme\"]\n", + "args = [\"duration\"]\n", + "iters = [[1, 1.5, 2, 2.5, 3]]\n", + "\n", + "seq1 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/william/.pyenv/versions/3.6.0/envs/qcodesdevel/lib/python3.6/site-packages/matplotlib/pyplot.py:524: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`).\n", + " max_open_warning, RuntimeWarning)\n" + ] + }, + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(bp1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Now make a variation of the height\n", + "\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "\n", + "elem2 = elem1.copy()\n", + "elem2.changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "elem2.changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "\n", + "elem3 = elem1.copy()\n", + "elem3.changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "elem3.changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "\n", + "# And put that together in a sequence\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Let's make a sequence instead of an element\n", + "\n", + "SR = 1e9\n", + "t1 = 200e-6 # wait\n", + "t2 = 20e-9 # perturb the system\n", + "t3 = 250e-6 # read out\n", + "\n", + "compression = 100 # this number has to be chosen with some care\n", + "\n", + "bp1 = bb.BluePrint()\n", + "bp1.insertSegment(0, ramp, (0, 0), dur=t1 / compression)\n", + "bp1.setSR(SR)\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "#\n", + "bp2 = bb.BluePrint()\n", + "bp2.insertSegment(0, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", + "bp2.setSR(SR)\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(1, bp2)\n", + "#\n", + "bp3 = bb.BluePrint()\n", + "bp3.insertSegment(0, ramp, (0, 0), dur=t3 / compression)\n", + "bp3.setSR(SR)\n", + "elem3 = bb.Element()\n", + "elem3.addBluePrint(1, bp3)\n", + "\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.setSequencingNumberOfRepetitions(1, compression)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSequencingNumberOfRepetitions(3, compression)\n", + "seq.setSR(SR)\n", + "\n", + "# Now make the variation\n", + "seq2 = seq.copy()\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "#\n", + "seq3 = seq.copy()\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "#\n", + "fullseq = seq + seq2 + seq3\n", + "plotter(fullseq)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13560" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The above sequence achieves the same as the uncompresed, but has fewer points\n", + "fullseq.points" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Now using subsequences\n", + "\n", + "Subsequences come into play when we want to, say, repeat each wait-perturb-wait element 25 times.\n", + "In the uncompressed case, that can only be achieved by adding each element 24 times more, thus resulting in a very large output file. Using subsequences, we can get away with a much smaller file size." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $(' +
+ +
+ + +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

broadbean package

+
+

Submodules

+
+
+

broadbean.blueprint module

+
+
+class broadbean.blueprint.BluePrint(funlist=None, argslist=None, namelist=None, marker1=None, marker2=None, segmentmarker1=None, segmentmarker2=None, SR=None, durslist=None)[source]
+

Bases: object

+

The class of a waveform to become.

+
+
+property SR
+

Sample rate of the blueprint

+
+ +
+
+classmethod blueprint_from_description(blue_dict)[source]
+

Returns a blueprint from a description given as a dict

+
+
Parameters:
+
    +
  • blue_dict – a dict in the same form as returned by

  • +
  • BluePrint.description

  • +
+
+
+
+ +
+
+changeArg(name, arg, value, replaceeverywhere=False)[source]
+

Change an argument of one or more of the functions in the blueprint.

+
+
Parameters:
+
    +
  • name (str) – The name of the segment in which to change an argument

  • +
  • arg (Union[int, str]) – Either the position (int) or name (str) of +the argument to change

  • +
  • value (Union[int, float]) – The new value of the argument

  • +
  • replaceeverywhere (bool) – If True, the same argument is overwritten +in ALL segments where the name matches. E.g. ‘gaussian1’ will +match ‘gaussian’, ‘gaussian2’, etc. If False, only the segment +with exact name match gets a replacement.

  • +
+
+
Raises:
+
    +
  • ValueError – If the argument can not be matched (either the argument + name does not match or the argument number is wrong).

  • +
  • ValueError – If the name can not be matched.

  • +
+
+
+
+ +
+
+changeDuration(name, dur, replaceeverywhere=False)[source]
+

Change the duration of one or more segments in the blueprint

+
+
Parameters:
+
    +
  • name (str) – The name of the segment in which to change duration

  • +
  • dur (Union[float, int]) – The new duration.

  • +
  • replaceeverywhere (Optional[bool]) – If True, the duration(s) +is(are) overwritten in ALL segments where the name matches. +E.g. ‘gaussian1’ will match ‘gaussian’, ‘gaussian2’, +etc. If False, only the segment with exact name match +gets a replacement.

  • +
+
+
Raises:
+
    +
  • ValueError – If durations are not specified for the blueprint

  • +
  • ValueError – If too many or too few durations are given.

  • +
  • ValueError – If no segment matches the name.

  • +
  • ValueError – If dur is not positive

  • +
  • ValueError – If SR is given for the blueprint and dur is less than + 1/SR.

  • +
+
+
+
+ +
+
+copy()[source]
+

Returns a copy of the BluePrint

+
+ +
+
+property description
+

Returns a dict describing the blueprint.

+
+ +
+
+property duration
+

The total duration of the BluePrint. If necessary, all the arrays +are built.

+
+ +
+
+property durations
+

The list of durations

+
+ +
+
+classmethod init_from_json(path_to_file: str) BluePrint[source]
+

Reads blueprint from JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to be read ex:

  • +
  • path_to_file/blueprint.json

  • +
  • write_to_json (by the function)

  • +
  • writen (The JSON file needs to be structured as if it was)

  • +
  • write_to_json

  • +
+
+
+
+ +
+
+insertSegment(pos, func, args=(), dur=None, name=None, durs=None)[source]
+

Insert a segment into the bluePrint.

+
+
Parameters:
+
    +
  • pos (int) – The position at which to add the segment. Counts like +a python list; 0 is first, -1 is last. Values below -1 are +not allowed, though.

  • +
  • func (function) – Function describing the segment. Must have its +duration as the last argument (unless its a special function).

  • +
  • args (Optional[Tuple[Any]]) – Tuple of arguments BESIDES duration. +Default: ()

  • +
  • dur (Optional[Union[int, float]]) – The duration of the +segment. Must be given UNLESS the segment is +‘waituntil’ or ‘ensureaverage_fixed_level’

  • +
  • Optional[str] (name) – Name of the segment. If none is given, +the segment will receive the name of its function, +possibly with a number appended.

  • +
+
+
Raises:
+
    +
  • ValueError – If the position is negative

  • +
  • ValueError – If the name ends in a number

  • +
+
+
+
+ +
+
+property length_segments
+

Returns the number of segments in the blueprint

+
+ +
+
+property points
+

The total number of points in the BluePrint. If necessary, +all the arrays are built.

+
+ +
+
+removeSegment(name)[source]
+

Remove the specified segment from the blueprint.

+
+
Parameters:
+

name (str) – The name of the segment to remove.

+
+
+
+ +
+
+removeSegmentMarker(name: str, markerID: int) None[source]
+

Remove all bound markers from a specific segment

+
+
Parameters:
+
    +
  • name (str) – Name of the segment

  • +
  • markerID (int) – Which marker channel to remove from (1 or 2).

  • +
  • number (int) – The number of the marker, in case several markers are +bound to one element. Default: 1 (the first marker).

  • +
+
+
+
+ +
+
+setSR(SR)[source]
+

Set the associated sample rate

+
+
Parameters:
+

SR (Union[int, float]) – The sample rate in Sa/s.

+
+
+
+ +
+
+setSegmentMarker(name, specs, markerID)[source]
+

Bind a marker to a specific segment.

+
+
Parameters:
+
    +
  • name (str) – Name of the segment

  • +
  • specs (tuple) – Marker specification tuple, (delay, duration), +where the delay is relative to the segment start

  • +
  • markerID (int) – Which marker channel to output on. Must be 1 or 2.

  • +
+
+
+
+ +
+
+showPrint()[source]
+

Pretty-print the contents of the BluePrint. Not finished.

+
+ +
+
+write_to_json(path_to_file: str) None[source]
+

Writes blueprint to JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to write to ex:

  • +
  • path_to_file/blueprint.json

  • +
+
+
+
+ +
+ +
+
+exception broadbean.blueprint.SegmentDurationError[source]
+

Bases: Exception

+
+ +
+
+

broadbean.broadbean module

+
+
+class broadbean.broadbean.PulseAtoms[source]
+

Bases: object

+

A class full of static methods. +The basic pulse shapes.

+

Any pulse shape function should return a list or an np.array +and have SR, npoints as its final two arguments.

+

Rounding errors are a real concern/pain in the business of +making waveforms of short duration (few samples). Therefore, +the PulseAtoms take the number of points rather than the +duration as input argument, so that all ambiguity can be handled +in one place (the _subelementBuilder)

+
+
+static arb_func(func: Callable, kwargs, SR, npts)[source]
+

This function is used to generate an arbitrary waveform from a function. +The function must be of the form f(time, **kwargs) where time is a numpy array and +kwargs is a dict that provides any additional parameters needed for the function.

+

Example:

+
func = lambda time, freq, ampl, off, phase: ampl*np.sin(freq*time+phase)+off
+kwargs = {'freq': 1e6, 'ampl': 1, 'off': 0, 'phase': 0}
+
+
+
+ +
+
+static gaussian(ampl, sigma, mu, offset, SR, npts)[source]
+

Returns a Gaussian of peak height ampl (when offset==0)

+

Is by default centred in the middle of the interval

+
+ +
+
+static gaussian_smooth_cutoff(ampl, sigma, mu, offset, SR, npts)[source]
+

Returns a Gaussian of peak height ampl (when offset==0)

+

Is by default centred in the middle of the interval

+

smooth cutoff by making offsetting the Gaussian so endpoint = 0 and normalizing the hight to 1

+
+ +
+
+static ramp(start, stop, SR, npts)[source]
+
+ +
+
+static sine(freq, ampl, off, phase, SR, npts)[source]
+
+ +
+
+static waituntil(dummy, SR, npts)[source]
+
+ +
+ +
+
+broadbean.broadbean.marked_for_deletion(replaced_by: str | None = None) Callable[source]
+

A decorator for functions we want to kill. The function still +gets called.

+
+ +
+
+

broadbean.element module

+
+
+class broadbean.element.Element[source]
+

Bases: object

+

Object representing an element. An element is a collection of waves that +are to be run simultaneously. The element consists of a number of channels +that are then each filled with anything of the appropriate length.

+
+
+property SR
+

Returns the sample rate, if well-defined. Else raises +an error about what went wrong.

+
+ +
+
+addArray(channel: int | str, waveform: ndarray, SR: int, **kwargs) None[source]
+

Add an array of voltage value to the element on the specified channel. +Overwrites whatever was there before. Markers can be specified via +the kwargs, i.e. the kwargs must specify arrays of markers. The names +can be ‘m1’, ‘m2’, ‘m3’, etc.

+
+
Parameters:
+
    +
  • channel – The channel number

  • +
  • waveform – The array of waveform values (V)

  • +
  • SR – The sample rate in Sa/s

  • +
+
+
+
+ +
+
+addBluePrint(channel: str | int, blueprint: BluePrint) None[source]
+

Add a blueprint to the element on the specified channel. +Overwrites whatever was there before.

+
+ +
+
+addFlags(channel: str | int, flags: Sequence[str | int]) None[source]
+

Adds flags for the specified channel. +List of 4 flags, each of which should be 0 or “” for ‘No change’, 1 or “H” for ‘High’, +2 or “L” for ‘Low’, 3 or “T” for ‘Toggle’, 4 or “P” for ‘Pulse’.

+
+ +
+
+changeArg(channel: str | int, name: str, arg: str | int, value: int | float, replaceeverywhere: bool = False) None[source]
+

Change the argument of a function of the blueprint on the specified +channel.

+
+
Parameters:
+
    +
  • channel – The channel where the blueprint sits.

  • +
  • name – The name of the segment in which to change an argument

  • +
  • arg – Either the position (int) or name (str) of +the argument to change

  • +
  • value – The new value of the argument

  • +
  • replaceeverywhere – If True, the same argument is overwritten +in ALL segments where the name matches. E.g. ‘gaussian1’ will +match ‘gaussian’, ‘gaussian2’, etc. If False, only the segment +with exact name match gets a replacement.

  • +
+
+
Raises:
+
    +
  • ValueError – If the specified channel has no blueprint.

  • +
  • ValueError – If the argument can not be matched (either the argument + name does not match or the argument number is wrong).

  • +
+
+
+
+ +
+
+changeDuration(channel: str | int, name: str, newdur: int | float, replaceeverywhere: bool = False) None[source]
+

Change the duration of a segment of the blueprint on the specified +channel

+
+
Parameters:
+
    +
  • channel – The channel holding the blueprint in question

  • +
  • name) – The name of the segment to modify

  • +
  • newdur – The new duration.

  • +
  • replaceeverywhere – If True, all segments +matching the base +name given will have their duration changed. If False, only the +segment with an exact name match will have its duration +changed. Default: False.

  • +
+
+
+
+ +
+
+property channels
+

The channels that has something on them

+
+ +
+
+copy()[source]
+

Return a copy of the element

+
+ +
+
+property description
+

Returns a dict describing the element.

+
+ +
+
+property duration
+

Returns the duration in seconds of the element, if said duration is +well-defined. Else raises an error.

+
+ +
+
+classmethod element_from_description(element_dict)[source]
+

Returns a blueprint from a description given as a dict

+
+
Parameters:
+
    +
  • element_dict – a dict in the same form as returned by

  • +
  • Element.description

  • +
+
+
+
+ +
+
+getArrays(includetime: bool = False) dict[int, dict[str, ndarray]][source]
+

Return arrays of the element. Heavily used by the Sequence.

+
+
Parameters:
+

includetime – Whether to include time arrays. They will have the key +‘time’. Time should be included when plotting, otherwise not.

+
+
Returns:
+

Dictionary with channel numbers (ints) as keys and forged +blueprints as values. A forged blueprint is a dict with +the mandatory key ‘wfm’ and optional keys ‘m1’, ‘m2’, ‘m3’ (etc) +and ‘time’.

+
+
Return type:
+

dict

+
+
+
+ +
+
+classmethod init_from_json(path_to_file: str) Element[source]
+

Reads Element from JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to be read ex:

  • +
  • path_to_file/Element.json

  • +
  • write_to_json (by the function)

  • +
  • writen (The JSON file needs to be structured as if it was)

  • +
  • write_to_json

  • +
+
+
+
+ +
+
+property points: int
+

Returns the number of points of each channel if that number is +well-defined. Else an error is raised.

+
+ +
+
+validateDurations()[source]
+

Check that all channels have the same specified duration, number of +points and sample rate.

+
+ +
+
+write_to_json(path_to_file: str) None[source]
+

Writes element to JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to write to ex:

  • +
  • path_to_file/element.json

  • +
+
+
+
+ +
+ +
+
+exception broadbean.element.ElementDurationError[source]
+

Bases: Exception

+
+ +
+
+

broadbean.plotting module

+
+
+broadbean.plotting.getSIScalingAndPrefix(minmax: tuple[float, float]) tuple[float, str][source]
+

Return the scaling exponent and unit prefix. E.g. (-2e-3, 1e-6) will +return (1e3, ‘m’)

+
+
Parameters:
+

minmax – The (min, max) value of the signal

+
+
Returns:
+

+
A tuple of the scaling (inverse of the prefix) and the prefix

string.

+
+
+

+
+
+
+ +
+
+broadbean.plotting.plotter(obj_to_plot: Sequence | BluePrint | Element, **forger_kwargs) None[source]
+

The one plot function to be called. Turns whatever it gets +into a sequence, forges it, and plots that.

+
+ +
+
+

broadbean.ripasso module

+
+
+exception broadbean.ripasso.MissingFrequenciesError[source]
+

Bases: Exception

+
+ +
+
+broadbean.ripasso.applyCustomTransferFunction(signal, SR, tf_freqs, tf_amp, invert=False)[source]
+

Apply custom transfer function

+

Given a signal, its sample rate, and a provided transfer function, apply +the transfer function to the signal.

+
+
Parameters:
+
    +
  • signal (np.array) – A numpy array containing the signal

  • +
  • SR (int) – The sample rate of the signal (Sa/s)

  • +
  • tf_freqs (np.array) – The frequencies of the transfer function. Must +be monotonically increasing.

  • +
  • tf_amp (np.array) – The amplitude of the transfer function. Must be +dimensionless.

  • +
  • invert (Optional[bool]) – If True, the inverse transfer function is +applied. Default: False.

  • +
+
+
Returns:
+

The modified signal.

+
+
Return type:
+

np.array

+
+
+
+ +
+
+broadbean.ripasso.applyInverseRCFilter(signal, SR, kind, f_cut, order, DCgain=1)[source]
+

Apply the inverse of an RC-circuit filter to a signal and return the +compensated signal.

+

Note that a high-pass filter in principle has identically zero DC +gain which requires an infinite offset to compensate.

+
+
Parameters:
+
    +
  • signal (np.array) – The input signal. The signal is assumed to start at +t=0 and be evenly sampled at sample rate SR.

  • +
  • SR (int) – Sample rate (Sa/s) of the input signal

  • +
  • kind (str) – The type of filter. Either ‘HP’ or ‘LP’.

  • +
  • f_cut (float) – The cutoff frequency of the filter (Hz)

  • +
  • order (int) – The order of the filter. The first order filter is +applied order times.

  • +
  • DCgain (Optional[float]) – The DC gain of the filter. ONLY used by the +high-pass filter. Default: 1.

  • +
+
+
Returns:
+

The filtered signal along the original time axis. Imaginary +parts are discarded prior to return.

+
+
Return type:
+

np.array

+
+
Raises:
+
    +
  • ValueError – If kind is neither ‘HP’ nor ‘LP’

  • +
  • ValueError – If DCgain is zero.

  • +
+
+
+
+ +
+
+broadbean.ripasso.applyRCFilter(signal, SR, kind, f_cut, order, DCgain=0)[source]
+

Apply a simple RC-circuit filter +to signal and return the filtered signal.

+
+
Parameters:
+
    +
  • signal (np.array) – The input signal. The signal is assumed to start at +t=0 and be evenly sampled at sample rate SR.

  • +
  • SR (int) – Sample rate (Sa/s) of the input signal

  • +
  • kind (str) – The type of filter. Either ‘HP’ or ‘LP’.

  • +
  • f_cut (float) – The cutoff frequency of the filter (Hz)

  • +
  • order (int) – The order of the filter. The first order filter is +applied order times.

  • +
  • DCgain (Optional[float]) – The DC gain of the filter. ONLY used by the +high-pass filter. Default: 0.

  • +
+
+
Returns:
+

The filtered signal along the original time axis. Imaginary +parts are discarded prior to return.

+
+
Return type:
+

np.array

+
+
Raises:
+

ValueError – If kind is neither ‘HP’ nor ‘LP’

+
+
+
+ +
+
+

broadbean.sequence module

+
+
+exception broadbean.sequence.InvalidForgedSequenceError[source]
+

Bases: Exception

+
+ +
+
+class broadbean.sequence.Sequence[source]
+

Bases: object

+

Sequence object

+
+
+property SR
+

Returns the sample rate, if defined. Else returns -1.

+
+ +
+
+addElement(position: int, element: Element) None[source]
+

Add an element to the sequence. Overwrites previous values.

+
+
Parameters:
+
    +
  • position (int) – The sequence position of the element (lowest: 1)

  • +
  • element (Element) – An element instance

  • +
+
+
Raises:
+

ValueError – If the element has inconsistent durations

+
+
+
+ +
+
+addSubSequence(position: int, subsequence: Sequence) None[source]
+

Add a subsequence to the sequence. Overwrites anything previously +assigned to this position. The subsequence can not contain any +subsequences itself.

+
+
Parameters:
+
    +
  • position – The sequence position (starting from 1)

  • +
  • subsequence – The subsequence to add

  • +
+
+
+
+ +
+
+property channels
+

Returns a list of the specified channels of the sequence

+
+ +
+
+checkConsistency(verbose=False)[source]
+

Checks wether the sequence can be built, i.e. wether all elements +have waveforms on the same channels and of the same length.

+
+ +
+
+copy()[source]
+

Returns a copy of the sequence.

+
+ +
+
+property description
+

Return a dictionary fully describing the Sequence.

+
+ +
+
+property duration: float
+

Returns the duration in seconds of the sequence.

+
+ +
+
+element(pos)[source]
+

Returns the element at the given position. Changes made to the return +value of this methods will apply to the sequence. If this is undesired, +make a copy of the returned element using Element.copy

+
+
Parameters:
+

pos (int) – The sequence position

+
+
Raises:
+

KeyError – If no element is specified at the given position

+
+
+
+ +
+
+forge(apply_delays: bool = True, apply_filters: bool = True, includetime: bool = False) dict[int, dict][source]
+

Forge the sequence, applying all specified transformations +(delays and ripasso filter corrections). Copies the data, so +that the sequence is not modified by forging.

+
+
Parameters:
+
    +
  • apply_delays – Whether to apply the assigned channel delays +(if any)

  • +
  • apply_filters – Whether to apply the assigned channel filters +(if any)

  • +
  • includetime – Whether to include the time axis and the segment +durations (a list) with the arrays. Used for plotting.

  • +
+
+
Returns:
+

A nested dictionary holding the forged sequence.

+
+
+
+ +
+
+classmethod init_from_json(path_to_file: str) Sequence[source]
+

Reads sequense from JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to be read ex:

  • +
  • path_to_file/sequense.json

  • +
  • write_to_json (by the function)

  • +
  • writen (The JSON file needs to be structured as if it was)

  • +
  • write_to_json

  • +
+
+
+
+ +
+
+property length_sequenceelements
+

Returns the current number of specified sequence elements

+
+ +
+
+property name
+
+ +
+
+outputForAWGFile()[source]
+

Returns a sliceable object with items matching the call +signature of the ‘make_*_awg_file’ functions of the QCoDeS +AWG5014 driver. One may then construct an awg file as follows +(assuming that seq is the sequence object):

+
package = seq.outputForAWGFile()
+make_awg_file(*package[:], **kwargs)
+
+
+
+ +
+
+outputForSEQXFile() tuple[list[int], list[int], list[int], list[int], list[int], list[list[ndarray]], list[float], str][source]
+

Generate a tuple matching the call signature of the QCoDeS +AWG70000A driver’s makeSEQXFile function. If channel delays +have been specified, they are added to the ouput before exporting. +The intended use of this function together with the QCoDeS driver is

+
pkg = seq.outputForSEQXFile()
+seqx = awg70000A.makeSEQXFile(*pkg)
+
+
+
+
Returns:
+

+
A tuple holding (trig_waits, nreps, event_jumps, event_jump_to,

go_to, wfms, amplitudes, seqname)

+
+
+

+
+
+
+ +
+
+outputForSEQXFileWithFlags() tuple[list[int], list[int], list[int], list[int], list[int], list[list[ndarray]], list[float], str, list[list[list[int]]]][source]
+

Generate a tuple matching the call signature of the QCoDeS +AWG70000A driver’s makeSEQXFile function. Same as outputForSEQXFile(), +but also includes information about the flags.

+
+
Returns:
+

+
A tuple holding (trig_waits, nreps, event_jumps, event_jump_to,

go_to, wfms, amplitudes, seqname, flags)

+
+
+

+
+
+
+ +
+
+property points
+

Returns the number of points of the sequence, disregarding +sequencing info (like repetitions). Useful for asserting upload +times, i.e. the size of the built sequence.

+
+ +
+
+classmethod sequence_from_description(seq_dict: dict) Sequence[source]
+

Returns a sequence from a description given as a dict

+
+
Parameters:
+
    +
  • seq_dict – a dict in the same form as returned by

  • +
  • Sequence.description

  • +
+
+
+
+ +
+
+setChannelAmplitude(channel: int | str, ampl: float) None[source]
+

Assign the physical voltage amplitude of the channel. This is used +when making output for real instruments.

+
+
Parameters:
+
    +
  • channel – The channel number

  • +
  • ampl – The channel peak-to-peak amplitude (V)

  • +
+
+
+
+ +
+
+setChannelDelay(channel: int | str, delay: float) None[source]
+

Assign a delay to a channel. This is used when making output for .awg +files. Use the delay to compensate for cable length differences etc. +Zeros are prepended to the waveforms to delay them and correspondingly +appended to non (or less) delayed channels.

+
+
Parameters:
+
    +
  • channel – The channel number/name

  • +
  • delay – The required delay (s)

  • +
+
+
Raises:
+

ValueError – If a non-integer or non-non-negative channel number is + given.

+
+
+
+ +
+
+setChannelFilterCompensation(channel: str | int, kind: str, order: int = 1, f_cut: float | None = None, tau: float | None = None) None[source]
+

Specify a filter to compensate for.

+

The specified channel will get a compensation (pre-distorion) to +compensate for the specified frequency filter. Just to be clear: +the INVERSE transfer function of the one you specify is applied. +Only compensation for simple RC-circuit type high pass and low +pass is supported.

+
+
Parameters:
+
    +
  • channel – The channel to apply this to.

  • +
  • kind – Either ‘LP’ or ‘HP’

  • +
  • order – The order of the filter to compensate for. +May be negative. Default: 1.

  • +
  • f_cut – The cut_off frequency (Hz).

  • +
  • tau) – The time constant (s). Note that +tau = 1/f_cut and that only one of the two can be specified.

  • +
+
+
Raises:
+
    +
  • ValueError – If kind is not ‘LP’ or ‘HP’

  • +
  • ValueError – If order is not an int.

  • +
  • SpecificationInconsistencyError – If both f_cut and tau are given.

  • +
+
+
+
+ +
+
+setChannelOffset(channel: int | str, offset: float) None[source]
+

Assign the physical voltage offset of the channel. This is used +by some backends when making output for real instruments

+
+
Parameters:
+
    +
  • channel – The channel number/name

  • +
  • offset – The channel offset (V)

  • +
+
+
+
+ +
+
+setChannelVoltageRange(channel, ampl, offset)[source]
+

Assign the physical voltages of the channel. This is used when making +output for .awg files. The corresponding parameters in the QCoDeS +AWG5014 driver are called chXX_amp and chXX_offset. Please ensure that +the channel in question is indeed in ampl/offset mode and not in +high/low mode.

+
+
Parameters:
+
    +
  • channel (int) – The channel number

  • +
  • ampl (float) – The channel peak-to-peak amplitude (V)

  • +
  • offset (float) – The channel offset (V)

  • +
+
+
+
+ +
+
+setSR(SR)[source]
+

Set the sample rate for the sequence

+
+ +
+
+setSequenceSettings(pos, wait, nreps, jump, goto)[source]
+

Set the sequence setting for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos (int) – The sequence element (counting from 1)

  • +
  • wait (int) – The wait state specifying whether to wait for a +trigger. 0: OFF, don’t wait, 1: ON, wait. For some backends, +additional integers are allowed to specify the trigger input. +0 always means off.

  • +
  • nreps (int) – Number of repetitions. 0 corresponds to infinite +repetitions

  • +
  • jump (int) – Event jump target, the position of a sequence element. +If 0, the event jump state is off.

  • +
  • goto (int) – Goto target, the position of a sequence element. +0 means next.

  • +
+
+
+
+ +
+
+setSequencingEventInput(pos: int, jump_input: int) None[source]
+

Set the event input for the sequence element at pos. This setting is +ignored by the AWG 5014.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • jump_input – The input specifier, 0 for off, +1 for ‘TrigA’, 2 for ‘TrigB’, 3 for ‘Internal’.

  • +
+
+
+
+ +
+
+setSequencingEventJumpTarget(pos: int, jump_target: int) None[source]
+

Set the event jump target for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • jump_target – The sequence element to jump to (counting from 1)

  • +
+
+
+
+ +
+
+setSequencingGoto(pos: int, goto: int) None[source]
+

Set the goto target (which element to play after the current one ends) +for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • goto – The position of the element to play. 0 means ‘next in line’

  • +
+
+
+
+ +
+
+setSequencingNumberOfRepetitions(pos: int, nrep: int) None[source]
+

Set the number of repetitions for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • nrep – The number of repetitions (0 means infinite)

  • +
+
+
+
+ +
+
+setSequencingTriggerWait(pos: int, wait: int) None[source]
+

Set the trigger wait for the sequence element at pos. For +AWG 5014 out, this can be 0 or 1, For AWG 70000A output, this +can be 0, 1, 2, or 3.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • wait – The wait state/input depending on backend.

  • +
+
+
+
+ +
+
+write_to_json(path_to_file: str) None[source]
+

Writes sequences to JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to write to ex:

  • +
  • path_to_file/sequense.json

  • +
+
+
+
+ +
+ +
+
+exception broadbean.sequence.SequenceCompatibilityError[source]
+

Bases: Exception

+
+ +
+
+exception broadbean.sequence.SequenceConsistencyError[source]
+

Bases: Exception

+
+ +
+
+exception broadbean.sequence.SequencingError[source]
+

Bases: Exception

+
+ +
+
+exception broadbean.sequence.SpecificationInconsistencyError[source]
+

Bases: Exception

+
+ +
+
+

broadbean.tools module

+
+
+broadbean.tools.makeLinearlyVaryingSequence(baseelement, channel, name, arg, start, stop, step)[source]
+

Make a pulse sequence where a single parameter varies linearly. +The pulse sequence will consist of N copies of the same element with just +the specified argument changed (N = abs(stop-start)/steps)

+
+
Parameters:
+
    +
  • baseelement (Element) – The basic element.

  • +
  • channel (int) – The channel where the change should happen

  • +
  • name (str) – Name of the blueprint segment to change

  • +
  • arg (Union[str, int]) – Name (str) or position (int) of the argument +to change. If the arg is ‘duration’, the duration is changed +instead.

  • +
  • start (float) – Start point of the variation (included)

  • +
  • stop (float) – Stop point of the variation (included)

  • +
  • step (float) – Increment of the variation

  • +
+
+
+
+ +
+
+broadbean.tools.makeVaryingSequence(baseelement, channels, names, args, iters)[source]
+

Make a pulse sequence where N parameters vary simultaneously in M steps. +The user inputs a baseelement which is copied M times and changed +according to the given inputs.

+
+
Parameters:
+
    +
  • baseelement (Element) – The basic element.

  • +
  • channels (Union[list, tuple]) – Either a list or a tuple of channels on +which to find the blueprint to change. Must have length N.

  • +
  • names (Union[list, tuple]) – Either a list or a tuple of names of the +segment to change. Must have length N.

  • +
  • args (Union[list, tuple]) – Either a list or a tuple of argument +specifications for the argument to change. Use ‘duration’ to change +the segment duration. Must have length N.

  • +
  • iters (Union[list, tuple]) – Either a list or a tuple of length N +containing Union[list, tuple, range] of length M.

  • +
+
+
Raises:
+
    +
  • ValueError – If not channels, names, args, and iters are of the same + length.

  • +
  • ValueError – If not each iter in iters specifies the same number of + values.

  • +
+
+
+
+ +
+
+broadbean.tools.repeatAndVarySequence(seq, poss, channels, names, args, iters)[source]
+

Repeat a sequence and vary part(s) of it. Returns a new sequence. +Given N specifications of M steps, N parameters are varied in M +steps.

+
+
Parameters:
+
    +
  • seq (Sequence) – The sequence to be repeated.

  • +
  • poss (Union[list, tuple]) – A length N list/tuple specifying at which +sequence position(s) the blueprint to change is.

  • +
  • channels (Union[list, tuple]) – A length N list/tuple specifying on +which channel(s) the blueprint to change is.

  • +
  • names (Union[list, tuple]) – A length N list/tuple specifying the name +of the segment to change.

  • +
  • args (Union[list, tuple]) – A length N list/tuple specifying which +argument to change. A valid argument is also ‘duration’.

  • +
  • iters (Union[list, tuple]) – A length N list/tuple containing length +M indexable iterables with the values to step through.

  • +
+
+
+
+ +
+
+

Module contents

+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/changes/0.10.0.html b/changes/0.10.0.html new file mode 100644 index 000000000..6ce02674f --- /dev/null +++ b/changes/0.10.0.html @@ -0,0 +1,349 @@ + + + + + + + + + Changelog for broadbean 0.10.0 - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Changelog for broadbean 0.10.0

+

The January 2021 release of broadbean.

+
+

Breaking Changes:

+

There are no breaking changes in this release of broadbean

+
+
+

New:

+
    +
  • Support for reading and writing broadbean Sequences, elements and bluprints to and from a json file

  • +
  • made broadbean compatiple with numpy version 1.18

  • +
  • Include LICENSE in package

  • +
+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/changes/0.11.0.html b/changes/0.11.0.html new file mode 100644 index 000000000..6d37874e5 --- /dev/null +++ b/changes/0.11.0.html @@ -0,0 +1,383 @@ + + + + + + + + + Changelog for broadbean 0.11.0 - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Changelog for broadbean 0.11.0

+

The August 2022 release of broadbean with many great improvements for modernizing broadbean’s infrastructure.

+
+

Breaking Changes:

+
    +
  • Methods and functions marked for deletion in version 0.10.0 have now been removed. Specifically +BluePrint.plot, broadbean.bluePrintPlotter, Element.plotElement, Sequence.plotSequence +and Sequence.plotAWGOutput. (#107)

  • +
+
+
+

New:

+
    +
  • New Sequence method outputForSEQXFileWithFlags for setting flags for every element forming a sequence. +This is useful for auxiliary outputs on a Tektronix AWG70000. (#101)

  • +
+
+
+

Improved:

+
    +
  • Fix for invalid identity comparisons in blueprint submodule. (#102)

  • +
+
+
+

Behind The Scenes:

+
    +
  • Updated README.md that includes updated link to broadbean documentation. (#158)

  • +
  • Replace Conda test job with regular pip job on windows. (#139)

  • +
  • Enable dependabot for broadbean. (#111)

  • +
  • Enable precommit hook. (#110)

  • +
  • Documentation infrastructure improvements. (#110)

  • +
  • Modernize setup and build infrastructure (convert to pep516/517, build wheels and sdist using build, +automatic upload to pypi, move config to pyproject.toml and setup.cfg and pinning dependencies with +requirements.txt). (#109)

  • +
  • Move tests into package to include them into distribution. (#108)

  • +
  • Use GitHub actions, test on python 3.7-3.10, remove python 3.6 support, remove Travis and AppVeyor. (#103)

  • +
+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/changes/index.html b/changes/index.html new file mode 100644 index 000000000..8570ade42 --- /dev/null +++ b/changes/index.html @@ -0,0 +1,340 @@ + + + + + + + + + Changelogs - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/examples/Example_Write_Read_JSON.html b/examples/Example_Write_Read_JSON.html new file mode 100644 index 000000000..c596e41e6 --- /dev/null +++ b/examples/Example_Write_Read_JSON.html @@ -0,0 +1,7727 @@ + + + + + + + + + Read and Write from JSON file Tutorial - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Read and Write from JSON file Tutorial

+
+
[1]:
+
+
+
#
+# IMPORTS
+#
+%matplotlib nbagg
+import matplotlib as mpl
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+mpl.rcParams["figure.figsize"] = (8, 3)
+mpl.rcParams["figure.subplot.bottom"] = 0.15
+
+
+
+
+

Read and Write BluePrint from JSON file Tutorial

+
+

Building a BluePrint

+
+
[2]:
+
+
+
# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+plotter(bp_boxes)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Writing the BluePrint to a file

+
+
[3]:
+
+
+
bp_boxes.write_to_json("blue.json")
+
+
+
+

Reading the BluePrint back from the file

+
+
[4]:
+
+
+
bp_boxes_read = bb.BluePrint.init_from_json("blue.json")
+bp_boxes_read.setSR(
+    1e9
+)  # note the blueprint readback do not have a sample rate attached
+plotter(bp_boxes_read)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Test if the BluePrints are identical

+
+
[5]:
+
+
+
print(bp_boxes == bp_boxes_read)
+
+
+
+
+
+
+
+
+True
+
+
+
+
+
+

Read and Write Element from JSON file Tutorial

+

Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array

+
+
[6]:
+
+
+
######################################################
+# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+sine = bb.PulseAtoms.sine  # args: freq, ampl, off, phase
+seq1 = bb.Sequence()
+
+# We fill up the sequence by adding elements at different sequence positions.
+# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3
+
+#
+# Make blueprints, make elements
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)
+bp_sineandboxes = bp_sine + bp_square
+
+bp_sineandboxes.setSegmentMarker(
+    "ramp", (-0.0, 100e-9), 1
+)  # segment name, (delay, duration), markerID
+bp_sineandboxes.setSegmentMarker(
+    "sine", (-0.0, 100e-9), 2
+)  # segment name, (delay, duration), markerID
+# make marker 2 go ON halfway through the sine
+# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)
+
+
+# create elements
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+plotter(elem1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Writing the Element to a file

+
+
[7]:
+
+
+
elem1.write_to_json("elem.json")
+plotter(elem1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Reading the Element back from the file

+
+
[8]:
+
+
+
elem_read = bb.Element.init_from_json("elem.json")
+
+
+
+

Test if the Element.description are identical

+
+
[9]:
+
+
+
print(
+    elem1.description == elem_read.description
+)  ## note that the elements are not identical only the discription
+
+
+
+
+
+
+
+
+True
+
+
+
+
+

Read and Write Sequence from JSON file Tutorial

+

Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array

+
+
[10]:
+
+
+
######################################################
+# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+sine = bb.PulseAtoms.sine  # args: freq, ampl, off, phase
+seq1 = bb.Sequence()
+
+# We fill up the sequence by adding elements at different sequence positions.
+# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3
+
+#
+# Make blueprints, make elements
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)
+bp_sineandboxes = bp_sine + bp_square
+
+bp_sineandboxes.setSegmentMarker(
+    "ramp", (-0.0, 100e-9), 1
+)  # segment name, (delay, duration), markerID
+bp_sineandboxes.setSegmentMarker(
+    "sine", (-0.0, 100e-9), 2
+)  # segment name, (delay, duration), markerID
+# make marker 2 go ON halfway through the sine
+# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)
+
+
+# create elements
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+#
+elem2 = bb.Element()
+elem2.addBluePrint(3, bp_boxes)
+elem2.addBluePrint(1, bp_sineandboxes)
+
+# Fill up the sequence
+seq1.addElement(1, elem1)  # Call signature: seq. pos., element
+seq1.addElement(2, elem2)
+seq1.addElement(3, elem1)
+
+# set its sample rate
+seq1.setSR(elem1.SR)
+
+seq1.setChannelAmplitude(1, 10e-3)  # Call signature: channel, amplitude (peak-to-peak)
+seq1.setChannelOffset(1, 0)
+seq1.setChannelAmplitude(3, 10e-3)
+seq1.setChannelOffset(3, 0)
+
+# Here we repeat each element twice and then proceed to the next, wrapping over at the end
+seq1.setSequencingTriggerWait(1, 0)
+seq1.setSequencingNumberOfRepetitions(1, 2)
+seq1.setSequencingEventJumpTarget(1, 0)
+seq1.setSequencingGoto(1, 2)
+#
+seq1.setSequencingTriggerWait(2, 0)
+seq1.setSequencingNumberOfRepetitions(2, 2)
+seq1.setSequencingEventJumpTarget(2, 0)
+seq1.setSequencingGoto(2, 3)
+#
+seq1.setSequencingTriggerWait(3, 0)
+seq1.setSequencingNumberOfRepetitions(3, 2)
+seq1.setSequencingEventJumpTarget(3, 0)
+seq1.setSequencingGoto(3, 1)
+plotter(seq1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Writing the Sequence to a file

+
+
[11]:
+
+
+
seq1.write_to_json("testdata.json")
+
+
+
+
+
[12]:
+
+
+
bp_boxes.write_to_json("blue.json")
+
+
+
+
+
+

Reading the Sequence back from the file

+
+
[13]:
+
+
+
seq = bb.Sequence.init_from_json("testdata.json")
+
+
+
+
+
[14]:
+
+
+
plotter(seq)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Test if the Sequences are identical

+
+
[15]:
+
+
+
print(seq == seq1)
+
+
+
+
+
+
+
+
+True
+
+
+
+
+
+
+

Ekstras

+

Example of how read and write blueprint from/to Sequence json file if needed.

+
+
[16]:
+
+
+
def readblueprint(path: str, element: int = 1, channel: int = 1):
+    tempseq = bb.Sequence.init_from_json(path)
+    return tempseq.element(element)._data[channel]["blueprint"]
+
+
+
+
+
[17]:
+
+
+
def writeblueprint(
+    blueprint, path: str, SR: float = 1e9, SeqAmp: float = 10e-3, SeqOffset: float = 0
+):  # -> None
+    if blueprint.SR is None:
+        blueprint.setSR(SR)
+    elemtmp = bb.Element()
+    seqtmp = bb.Sequence()
+    elemtmp.addBluePrint(1, blueprint)
+    seqtmp.addElement(1, elemtmp)
+    seqtmp.setSR(blueprint.SR)
+    seqtmp.setChannelAmplitude(1, SeqAmp)
+    seqtmp.setChannelOffset(1, 0)
+    seqtmp.write_to_json(path)
+
+
+
+
+
[18]:
+
+
+
writeblueprint(bp_boxes, "boxes.json")
+
+
+
+
+
[19]:
+
+
+
bp_one_two = readblueprint("testdata.json", element=2, channel=1)
+plotter(bp_one_two)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[ ]:
+
+
+

+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Example_Write_Read_JSON.ipynb b/examples/Example_Write_Read_JSON.ipynb new file mode 100644 index 000000000..d7503078d --- /dev/null +++ b/examples/Example_Write_Read_JSON.ipynb @@ -0,0 +1,7677 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Read and Write from JSON file Tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:45.984685Z", + "iopub.status.busy": "2025-01-20T06:27:45.984525Z", + "iopub.status.idle": "2025-01-20T06:27:46.362493Z", + "shell.execute_reply": "2025-01-20T06:27:46.361965Z" + } + }, + "outputs": [], + "source": [ + "#\n", + "# IMPORTS\n", + "#\n", + "%matplotlib nbagg\n", + "import matplotlib as mpl\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write BluePrint from JSON file Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Building a BluePrint" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.364599Z", + "iopub.status.busy": "2025-01-20T06:27:46.364372Z", + "iopub.status.idle": "2025-01-20T06:27:46.387437Z", + "shell.execute_reply": "2025-01-20T06:27:46.386891Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "plotter(bp_boxes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the BluePrint to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.407329Z", + "iopub.status.busy": "2025-01-20T06:27:46.407118Z", + "iopub.status.idle": "2025-01-20T06:27:46.410693Z", + "shell.execute_reply": "2025-01-20T06:27:46.410122Z" + } + }, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the BluePrint back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.412617Z", + "iopub.status.busy": "2025-01-20T06:27:46.412223Z", + "iopub.status.idle": "2025-01-20T06:27:46.433658Z", + "shell.execute_reply": "2025-01-20T06:27:46.433143Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_boxes_read = bb.BluePrint.init_from_json(\"blue.json\")\n", + "bp_boxes_read.setSR(\n", + " 1e9\n", + ") # note the blueprint readback do not have a sample rate attached\n", + "plotter(bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the BluePrints are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.435194Z", + "iopub.status.busy": "2025-01-20T06:27:46.435022Z", + "iopub.status.idle": "2025-01-20T06:27:46.438631Z", + "shell.execute_reply": "2025-01-20T06:27:46.438185Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(bp_boxes == bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Element from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.440191Z", + "iopub.status.busy": "2025-01-20T06:27:46.440028Z", + "iopub.status.idle": "2025-01-20T06:27:46.485501Z", + "shell.execute_reply": "2025-01-20T06:27:46.484899Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the Element to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.487168Z", + "iopub.status.busy": "2025-01-20T06:27:46.486994Z", + "iopub.status.idle": "2025-01-20T06:27:46.529124Z", + "shell.execute_reply": "2025-01-20T06:27:46.528522Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "elem1.write_to_json(\"elem.json\")\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the Element back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.531018Z", + "iopub.status.busy": "2025-01-20T06:27:46.530547Z", + "iopub.status.idle": "2025-01-20T06:27:46.535319Z", + "shell.execute_reply": "2025-01-20T06:27:46.534733Z" + } + }, + "outputs": [], + "source": [ + "elem_read = bb.Element.init_from_json(\"elem.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the Element.description are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.537169Z", + "iopub.status.busy": "2025-01-20T06:27:46.536885Z", + "iopub.status.idle": "2025-01-20T06:27:46.541071Z", + "shell.execute_reply": "2025-01-20T06:27:46.540503Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(\n", + " elem1.description == elem_read.description\n", + ") ## note that the elements are not identical only the discription" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Sequence from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.542749Z", + "iopub.status.busy": "2025-01-20T06:27:46.542446Z", + "iopub.status.idle": "2025-01-20T06:27:46.639341Z", + "shell.execute_reply": "2025-01-20T06:27:46.638866Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "#\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(3, bp_boxes)\n", + "elem2.addBluePrint(1, bp_sineandboxes)\n", + "\n", + "# Fill up the sequence\n", + "seq1.addElement(1, elem1) # Call signature: seq. pos., element\n", + "seq1.addElement(2, elem2)\n", + "seq1.addElement(3, elem1)\n", + "\n", + "# set its sample rate\n", + "seq1.setSR(elem1.SR)\n", + "\n", + "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", + "seq1.setChannelOffset(1, 0)\n", + "seq1.setChannelAmplitude(3, 10e-3)\n", + "seq1.setChannelOffset(3, 0)\n", + "\n", + "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", + "seq1.setSequencingTriggerWait(1, 0)\n", + "seq1.setSequencingNumberOfRepetitions(1, 2)\n", + "seq1.setSequencingEventJumpTarget(1, 0)\n", + "seq1.setSequencingGoto(1, 2)\n", + "#\n", + "seq1.setSequencingTriggerWait(2, 0)\n", + "seq1.setSequencingNumberOfRepetitions(2, 2)\n", + "seq1.setSequencingEventJumpTarget(2, 0)\n", + "seq1.setSequencingGoto(2, 3)\n", + "#\n", + "seq1.setSequencingTriggerWait(3, 0)\n", + "seq1.setSequencingNumberOfRepetitions(3, 2)\n", + "seq1.setSequencingEventJumpTarget(3, 0)\n", + "seq1.setSequencingGoto(3, 1)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing the Sequence to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.641206Z", + "iopub.status.busy": "2025-01-20T06:27:46.640881Z", + "iopub.status.idle": "2025-01-20T06:27:46.646056Z", + "shell.execute_reply": "2025-01-20T06:27:46.645591Z" + } + }, + "outputs": [], + "source": [ + "seq1.write_to_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.647727Z", + "iopub.status.busy": "2025-01-20T06:27:46.647426Z", + "iopub.status.idle": "2025-01-20T06:27:46.651430Z", + "shell.execute_reply": "2025-01-20T06:27:46.650984Z" + } + }, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reading the Sequence back from the file " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.653106Z", + "iopub.status.busy": "2025-01-20T06:27:46.652797Z", + "iopub.status.idle": "2025-01-20T06:27:46.659737Z", + "shell.execute_reply": "2025-01-20T06:27:46.659178Z" + } + }, + "outputs": [], + "source": [ + "seq = bb.Sequence.init_from_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.661318Z", + "iopub.status.busy": "2025-01-20T06:27:46.661012Z", + "iopub.status.idle": "2025-01-20T06:27:46.788304Z", + "shell.execute_reply": "2025-01-20T06:27:46.787713Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(seq)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test if the Sequences are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.790362Z", + "iopub.status.busy": "2025-01-20T06:27:46.790028Z", + "iopub.status.idle": "2025-01-20T06:27:46.794582Z", + "shell.execute_reply": "2025-01-20T06:27:46.794010Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(seq == seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Ekstras\n", + "Example of how read and write blueprint from/to Sequence json file if needed." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.796105Z", + "iopub.status.busy": "2025-01-20T06:27:46.795944Z", + "iopub.status.idle": "2025-01-20T06:27:46.800316Z", + "shell.execute_reply": "2025-01-20T06:27:46.799757Z" + } + }, + "outputs": [], + "source": [ + "def readblueprint(path: str, element: int = 1, channel: int = 1):\n", + " tempseq = bb.Sequence.init_from_json(path)\n", + " return tempseq.element(element)._data[channel][\"blueprint\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.802060Z", + "iopub.status.busy": "2025-01-20T06:27:46.801593Z", + "iopub.status.idle": "2025-01-20T06:27:46.806274Z", + "shell.execute_reply": "2025-01-20T06:27:46.805695Z" + } + }, + "outputs": [], + "source": [ + "def writeblueprint(\n", + " blueprint, path: str, SR: float = 1e9, SeqAmp: float = 10e-3, SeqOffset: float = 0\n", + "): # -> None\n", + " if blueprint.SR is None:\n", + " blueprint.setSR(SR)\n", + " elemtmp = bb.Element()\n", + " seqtmp = bb.Sequence()\n", + " elemtmp.addBluePrint(1, blueprint)\n", + " seqtmp.addElement(1, elemtmp)\n", + " seqtmp.setSR(blueprint.SR)\n", + " seqtmp.setChannelAmplitude(1, SeqAmp)\n", + " seqtmp.setChannelOffset(1, 0)\n", + " seqtmp.write_to_json(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.808098Z", + "iopub.status.busy": "2025-01-20T06:27:46.807761Z", + "iopub.status.idle": "2025-01-20T06:27:46.812258Z", + "shell.execute_reply": "2025-01-20T06:27:46.811818Z" + } + }, + "outputs": [], + "source": [ + "writeblueprint(bp_boxes, \"boxes.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:46.813897Z", + "iopub.status.busy": "2025-01-20T06:27:46.813548Z", + "iopub.status.idle": "2025-01-20T06:27:46.834955Z", + "shell.execute_reply": "2025-01-20T06:27:46.834352Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_one_two = readblueprint(\"testdata.json\", element=2, channel=1)\n", + "plotter(bp_one_two)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/Filter_compensation.html b/examples/Filter_compensation.html new file mode 100644 index 000000000..2c112818b --- /dev/null +++ b/examples/Filter_compensation.html @@ -0,0 +1,4591 @@ + + + + + + + + + Filter compensation with the ripasso module - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Filter compensation with the ripasso module

+

The broadbean module gets a helping hand from the ripasso module when it needs to compensate for transfer functions of transmission lines.

+

The ripasso module lets us apply filters and their inverses to signals.

+

The module has two built-in types of filter, namely an RC-circuit high-pass and an RC-circuit low-pass. The transfer functions are

+
+
+\[H_n(f)=\left(\frac{2\pi f\mathrm{i}\tau}{1+2\pi f\mathrm{i}\tau }\right)^{n},\]
+
+

for the nth order high-pass and

+
+
+\[L_n(f)=\left(\frac{1}{1+2\pi f\mathrm{i}\tau }\right)^{n},\]
+
+

for the nth order low-pass. The parameter \(\tau\) is one over the cut-off frequency.

+

The inversion (filter compensation) is performed by mulitplying with the inverse transfer function in frequency space and transforming back to the time domain, e.g. for a given signal \(s(t)\) and a high-pass filter of order n,

+
+
+\[s_\text{filtered}(t) = \mathcal{F}^{-1}[\mathcal{F}[s](f)\cdot H_n(f)](t)\]
+
+

and

+
+
+\[s_\text{compensated}(t) = \mathcal{F}^{-1}[\mathcal{F}[s](f)\cdot H_{-n}(f)](t).\]
+
+
+
[1]:
+
+
+
%matplotlib notebook
+import matplotlib.pyplot as plt
+import numpy as np
+
+from broadbean.ripasso import (
+    applyCustomTransferFunction,
+    applyInverseRCFilter,
+    applyRCFilter,
+)
+
+
+
+
+
[2]:
+
+
+
def squarewave(npts, periods=5):
+    periods = int(periods)
+    array = np.zeros(npts)
+
+    for n in range(periods):
+        array[int(n * npts / periods) : int((2 * n + 1) * npts / 2 / periods)] = 1
+
+    return array
+
+
+
+
+
+

RC Filters

+
+
[3]:
+
+
+
# Let's make some signals!
+
+SR = int(10e3)
+npts = int(10e3)
+
+f_cut = 12  # filter cut-off frequency (Hz)
+
+time = np.linspace(0, npts / SR, npts)
+T = time[-1]
+
+signal1 = np.sin(2 * np.pi * 5 / T * time) + 0.7 * np.cos(2 * np.pi * 1 / T * time)
+signal2 = squarewave(npts, periods=4)
+
+
+
+
+
[4]:
+
+
+
# Then we may high-pass them or low-pass them
+
+filtertype = "HP"
+# filtertype = 'LP'
+
+signal1_filt = applyRCFilter(signal1, SR, filtertype, f_cut, order=1)
+signal2_filt = applyRCFilter(signal2, SR, filtertype, f_cut, order=1)
+
+
+
+
+
[5]:
+
+
+
# And inspect the result
+
+fig, axs = plt.subplots(2, 1)
+axs[0].plot(time, signal1, label="Input signal")
+axs[0].plot(time, signal1_filt, label="Filtered")
+axs[0].set_xlabel("Time (s)")
+axs[0].set_ylabel("Signal (a.u.)")
+axs[0].legend()
+
+axs[1].plot(time, signal2, label="Input signal")
+axs[1].plot(time, signal2_filt, label="Filtered")
+axs[1].set_xlabel("Time (s)")
+axs[1].set_ylabel("Signal (a.u.)")
+axs[1].legend()
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[6]:
+
+
+
# Similarly, we may pre-compensate for the filters
+
+# Note that we specify a DC gain for the inverse filters. This is because it is not possible
+# to compensate for the true DC gain of zero of an RC high pass. Instead, we specify the
+# gain of the high-pass filter at DC. The compensated signal will have a mean value of 1/DCgain
+# times the mean of the input signal
+
+
+signal1_comp = applyInverseRCFilter(signal1, SR, filtertype, f_cut, order=1, DCgain=1)
+signal2_comp = applyInverseRCFilter(signal2, SR, filtertype, f_cut, order=1, DCgain=1)
+
+
+
+
+
[7]:
+
+
+
fig, axs = plt.subplots(2, 1)
+axs[0].plot(time, signal1, label="Input signal")
+axs[0].plot(time, signal1_comp, label="Compensated")
+axs[0].set_xlabel("Time (s)")
+axs[0].set_ylabel("Signal (a.u.)")
+axs[0].legend()
+
+axs[1].plot(time, signal2, label="Input signal")
+axs[1].plot(time, signal2_comp, label="Compensated")
+axs[1].set_xlabel("Time (s)")
+axs[1].set_ylabel("Signal (a.u.)")
+axs[1].legend()
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[8]:
+
+
+
# As a sanity-check, we may filter and compensate and compare
+#
+# Do we recover the original signals?
+
+signal1_check1 = applyRCFilter(signal1_comp, SR, filtertype, f_cut, order=1)
+signal1_check2 = applyInverseRCFilter(signal1_filt, SR, filtertype, f_cut, order=1)
+
+signal2_check1 = applyRCFilter(signal2_comp, SR, filtertype, f_cut, order=1)
+signal2_check2 = applyInverseRCFilter(signal2_filt, SR, filtertype, f_cut, order=1)
+
+fig, axs = plt.subplots(2, 1)
+axs[0].set_title("Signal 1")
+axs[0].plot(time, signal1_check1, label="Check 1")
+axs[0].plot(time, signal1_check2, label="Check 2")
+axs[0].legend()
+axs[0].set_xlabel("Time (s)")
+axs[0].set_ylabel("Signal (a. u.)")
+
+axs[1].set_title("Signal 2")
+axs[1].plot(time, signal2_check1, label="Check 1")
+axs[1].plot(time, signal2_check2, label="Check 2")
+axs[1].legend()
+axs[1].set_xlabel("Time (s)")
+axs[1].set_ylabel("Signal (a. u.)")
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

User-supplied transfer function

+

In an experimental situation, it is sometimes best to simply measure the transfer function of the transmission line and then apply (the inverse of) that transfer function to the system.

+

This is done with ripasso’s applyCustomTransferFunction.

+

In the following example, we invite the user to experiment with tf_noise_amp to see the drastic effect of low filter function values. In a real experiment, some care should be taken to provide a very smooth transfer function, either by heavy averaging or by curve fitting to a sane model (or both).

+
+
[9]:
+
+
+
# Lets first make some semi-realistic transfer function
+
+tf_points = 500
+
+tf_freq = np.linspace(0, 1000, tf_points)
+tf_amp = np.zeros(tf_points)
+tf_amp += np.hanning(2 * tf_points)[tf_points:][::-1]  # A high-pass
+tf_amp += (
+    0.1  # Note: a transfer function with very low values give unphysical compensation
+)
+tf_amp /= tf_amp.max()
+
+# and some experimental noise
+tf_noise_amp = 0.02
+tf_amp += (
+    np.convolve(0.02 * np.random.randn(tf_points), np.array([0.5, 1, 0.5]), mode="same")
+    / 2
+)
+
+# And then our favourite signal: the square wave
+
+sig_points = 1000
+signal = squarewave(sig_points, periods=10) + 0.01 * np.random.randn(1000)
+SR = 2000  # We pretend that the signal was sampled with this sample rate
+time = np.linspace(0, sig_points / SR, sig_points)
+
+# Then we may apply the filter we made
+
+signal_filtered = applyCustomTransferFunction(signal, SR, tf_freq, tf_amp)
+
+# Or conversely, we may apply its inverse
+
+signal_compensated = applyCustomTransferFunction(
+    signal, SR, tf_freq, tf_amp, invert=True
+)
+
+# Finally, it's nice to visualise things
+
+fig, axs = plt.subplots(2, 2)
+axs[0, 0].plot(tf_freq, tf_amp)
+axs[0, 0].set_xlabel("Freq. (Hz)")
+axs[0, 0].set_ylabel("Transfer func.")
+
+axs[0, 1].plot(time, signal, color="#ff9900")
+axs[0, 1].set_xlabel("Time (s)")
+axs[0, 1].set_ylabel("Sig. ampl. (arb. un.)")
+axs[0, 1].set_title("Input")
+
+axs[1, 0].plot(time, signal_filtered, color="#339966")
+axs[1, 0].set_xlabel("Time (s)")
+axs[1, 0].set_ylabel("Sig. ampl. (arb. un.)")
+axs[1, 0].set_title("Filtered")
+
+axs[1, 1].plot(time, signal_compensated, color="#990033")
+axs[1, 1].set_xlabel("Time (s)")
+axs[1, 1].set_ylabel("Sig.ampl. (arb. un.)")
+axs[1, 1].set_title("Compensated")
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[ ]:
+
+
+

+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Filter_compensation.ipynb b/examples/Filter_compensation.ipynb new file mode 100644 index 000000000..128d0ed64 --- /dev/null +++ b/examples/Filter_compensation.ipynb @@ -0,0 +1,4416 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filter compensation with the ripasso module\n", + "\n", + "The broadbean module gets a helping hand from the ripasso module when it needs to compensate for transfer functions of transmission lines.\n", + "\n", + "The ripasso module lets us apply filters and their inverses to signals.\n", + "\n", + "The module has two built-in types of filter, namely an RC-circuit high-pass and an RC-circuit low-pass. The transfer functions are\n", + "\n", + "$$\n", + "H_n(f)=\\left(\\frac{2\\pi f\\mathrm{i}\\tau}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order high-pass and\n", + "$$\n", + "L_n(f)=\\left(\\frac{1}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order low-pass. The parameter $\\tau$ is one over the cut-off frequency.\n", + "\n", + "The inversion (filter compensation) is performed by mulitplying with the inverse transfer function in frequency space and transforming back to the time domain, e.g. for a given signal $s(t)$ and a high-pass filter of order n,\n", + "\n", + "$$\n", + " s_\\text{filtered}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_n(f)](t)\n", + "$$\n", + "and\n", + "$$\n", + " s_\\text{compensated}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_{-n}(f)](t).\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:51.799540Z", + "iopub.status.busy": "2025-01-20T06:27:51.799370Z", + "iopub.status.idle": "2025-01-20T06:27:52.175289Z", + "shell.execute_reply": "2025-01-20T06:27:52.174759Z" + } + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from broadbean.ripasso import (\n", + " applyCustomTransferFunction,\n", + " applyInverseRCFilter,\n", + " applyRCFilter,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.177459Z", + "iopub.status.busy": "2025-01-20T06:27:52.177038Z", + "iopub.status.idle": "2025-01-20T06:27:52.180418Z", + "shell.execute_reply": "2025-01-20T06:27:52.179977Z" + } + }, + "outputs": [], + "source": [ + "def squarewave(npts, periods=5):\n", + " periods = int(periods)\n", + " array = np.zeros(npts)\n", + "\n", + " for n in range(periods):\n", + " array[int(n * npts / periods) : int((2 * n + 1) * npts / 2 / periods)] = 1\n", + "\n", + " return array" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## RC Filters" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.182154Z", + "iopub.status.busy": "2025-01-20T06:27:52.181829Z", + "iopub.status.idle": "2025-01-20T06:27:52.185963Z", + "shell.execute_reply": "2025-01-20T06:27:52.185542Z" + } + }, + "outputs": [], + "source": [ + "# Let's make some signals!\n", + "\n", + "SR = int(10e3)\n", + "npts = int(10e3)\n", + "\n", + "f_cut = 12 # filter cut-off frequency (Hz)\n", + "\n", + "time = np.linspace(0, npts / SR, npts)\n", + "T = time[-1]\n", + "\n", + "signal1 = np.sin(2 * np.pi * 5 / T * time) + 0.7 * np.cos(2 * np.pi * 1 / T * time)\n", + "signal2 = squarewave(npts, periods=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.187576Z", + "iopub.status.busy": "2025-01-20T06:27:52.187245Z", + "iopub.status.idle": "2025-01-20T06:27:52.191535Z", + "shell.execute_reply": "2025-01-20T06:27:52.191123Z" + } + }, + "outputs": [], + "source": [ + "# Then we may high-pass them or low-pass them\n", + "\n", + "filtertype = \"HP\"\n", + "# filtertype = 'LP'\n", + "\n", + "signal1_filt = applyRCFilter(signal1, SR, filtertype, f_cut, order=1)\n", + "signal2_filt = applyRCFilter(signal2, SR, filtertype, f_cut, order=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.193194Z", + "iopub.status.busy": "2025-01-20T06:27:52.192864Z", + "iopub.status.idle": "2025-01-20T06:27:52.280609Z", + "shell.execute_reply": "2025-01-20T06:27:52.280175Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# And inspect the result\n", + "\n", + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_filt, label=\"Filtered\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", + "axs[0].legend()\n", + "\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_filt, label=\"Filtered\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", + "axs[1].legend()\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.282389Z", + "iopub.status.busy": "2025-01-20T06:27:52.282050Z", + "iopub.status.idle": "2025-01-20T06:27:52.286789Z", + "shell.execute_reply": "2025-01-20T06:27:52.286221Z" + } + }, + "outputs": [], + "source": [ + "# Similarly, we may pre-compensate for the filters\n", + "\n", + "# Note that we specify a DC gain for the inverse filters. This is because it is not possible\n", + "# to compensate for the true DC gain of zero of an RC high pass. Instead, we specify the\n", + "# gain of the high-pass filter at DC. The compensated signal will have a mean value of 1/DCgain\n", + "# times the mean of the input signal\n", + "\n", + "\n", + "signal1_comp = applyInverseRCFilter(signal1, SR, filtertype, f_cut, order=1, DCgain=1)\n", + "signal2_comp = applyInverseRCFilter(signal2, SR, filtertype, f_cut, order=1, DCgain=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.288347Z", + "iopub.status.busy": "2025-01-20T06:27:52.288196Z", + "iopub.status.idle": "2025-01-20T06:27:52.368245Z", + "shell.execute_reply": "2025-01-20T06:27:52.367796Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_comp, label=\"Compensated\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", + "axs[0].legend()\n", + "\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_comp, label=\"Compensated\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", + "axs[1].legend()\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.369863Z", + "iopub.status.busy": "2025-01-20T06:27:52.369583Z", + "iopub.status.idle": "2025-01-20T06:27:52.458215Z", + "shell.execute_reply": "2025-01-20T06:27:52.457634Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# As a sanity-check, we may filter and compensate and compare\n", + "#\n", + "# Do we recover the original signals?\n", + "\n", + "signal1_check1 = applyRCFilter(signal1_comp, SR, filtertype, f_cut, order=1)\n", + "signal1_check2 = applyInverseRCFilter(signal1_filt, SR, filtertype, f_cut, order=1)\n", + "\n", + "signal2_check1 = applyRCFilter(signal2_comp, SR, filtertype, f_cut, order=1)\n", + "signal2_check2 = applyInverseRCFilter(signal2_filt, SR, filtertype, f_cut, order=1)\n", + "\n", + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].set_title(\"Signal 1\")\n", + "axs[0].plot(time, signal1_check1, label=\"Check 1\")\n", + "axs[0].plot(time, signal1_check2, label=\"Check 2\")\n", + "axs[0].legend()\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a. u.)\")\n", + "\n", + "axs[1].set_title(\"Signal 2\")\n", + "axs[1].plot(time, signal2_check1, label=\"Check 1\")\n", + "axs[1].plot(time, signal2_check2, label=\"Check 2\")\n", + "axs[1].legend()\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a. u.)\")\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## User-supplied transfer function\n", + "\n", + "In an experimental situation, it is sometimes best to simply _measure_ the transfer function of the transmission line and then apply (the inverse of) that transfer function to the system.\n", + "\n", + "This is done with ripasso's `applyCustomTransferFunction`.\n", + "\n", + "In the following example, we invite the user to experiment with `tf_noise_amp` to see the drastic effect of low filter function values. In a real experiment, some care should be taken to provide a very smooth transfer function, either by heavy averaging or by curve fitting to a sane model (or both)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:52.460125Z", + "iopub.status.busy": "2025-01-20T06:27:52.459700Z", + "iopub.status.idle": "2025-01-20T06:27:52.583869Z", + "shell.execute_reply": "2025-01-20T06:27:52.583302Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Lets first make some semi-realistic transfer function\n", + "\n", + "tf_points = 500\n", + "\n", + "tf_freq = np.linspace(0, 1000, tf_points)\n", + "tf_amp = np.zeros(tf_points)\n", + "tf_amp += np.hanning(2 * tf_points)[tf_points:][::-1] # A high-pass\n", + "tf_amp += (\n", + " 0.1 # Note: a transfer function with very low values give unphysical compensation\n", + ")\n", + "tf_amp /= tf_amp.max()\n", + "\n", + "# and some experimental noise\n", + "tf_noise_amp = 0.02\n", + "tf_amp += (\n", + " np.convolve(0.02 * np.random.randn(tf_points), np.array([0.5, 1, 0.5]), mode=\"same\")\n", + " / 2\n", + ")\n", + "\n", + "# And then our favourite signal: the square wave\n", + "\n", + "sig_points = 1000\n", + "signal = squarewave(sig_points, periods=10) + 0.01 * np.random.randn(1000)\n", + "SR = 2000 # We pretend that the signal was sampled with this sample rate\n", + "time = np.linspace(0, sig_points / SR, sig_points)\n", + "\n", + "# Then we may apply the filter we made\n", + "\n", + "signal_filtered = applyCustomTransferFunction(signal, SR, tf_freq, tf_amp)\n", + "\n", + "# Or conversely, we may apply its inverse\n", + "\n", + "signal_compensated = applyCustomTransferFunction(\n", + " signal, SR, tf_freq, tf_amp, invert=True\n", + ")\n", + "\n", + "# Finally, it's nice to visualise things\n", + "\n", + "fig, axs = plt.subplots(2, 2)\n", + "axs[0, 0].plot(tf_freq, tf_amp)\n", + "axs[0, 0].set_xlabel(\"Freq. (Hz)\")\n", + "axs[0, 0].set_ylabel(\"Transfer func.\")\n", + "\n", + "axs[0, 1].plot(time, signal, color=\"#ff9900\")\n", + "axs[0, 1].set_xlabel(\"Time (s)\")\n", + "axs[0, 1].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[0, 1].set_title(\"Input\")\n", + "\n", + "axs[1, 0].plot(time, signal_filtered, color=\"#339966\")\n", + "axs[1, 0].set_xlabel(\"Time (s)\")\n", + "axs[1, 0].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[1, 0].set_title(\"Filtered\")\n", + "\n", + "axs[1, 1].plot(time, signal_compensated, color=\"#990033\")\n", + "axs[1, 1].set_xlabel(\"Time (s)\")\n", + "axs[1, 1].set_ylabel(\"Sig.ampl. (arb. un.)\")\n", + "axs[1, 1].set_title(\"Compensated\")\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Making_output_for_Tektronix_AWG70000A.html b/examples/Making_output_for_Tektronix_AWG70000A.html new file mode 100644 index 000000000..92b8feadb --- /dev/null +++ b/examples/Making_output_for_Tektronix_AWG70000A.html @@ -0,0 +1,1729 @@ + + + + + + + + + Introduction - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Introduction

+

The Tektronix AWG70000A series internally use .seqx files. The QCoDeS driver for the AWGs knows how to compile these files and broadbean knows what QCoDeS need to do so.

+
+

Step 1: Make a sequence

+
+
[1]:
+
+
+
# We make a sequence consisting of a bsic element with:
+# a wait time, a ramp up, a sine, a ramp down, and more wait time
+# And then we vary the sine frequency
+%matplotlib notebook
+import numpy as np
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+sine = bb.PulseAtoms.sine
+ramp = bb.PulseAtoms.ramp
+
+##########################
+# settings
+
+SR = 25e9
+t1 = 10e-9  # first wait time (s)
+ramp_time = 5e-9  # the ramp time (s)
+t_sine = 25e-9  # the sine time (s)
+high_level = 0.1  # the high level (V)
+sine_amp = 0.05  # the sine amplitude (V)
+t2 = 150e-9  # the second wait time (s)
+f0 = 1e9  # the base frequency of the sine (Hz)
+
+baseshape = bb.BluePrint()
+baseshape.insertSegment(0, ramp, (0, 0), dur=t1)
+baseshape.insertSegment(1, ramp, (0, high_level), dur=ramp_time)
+baseshape.insertSegment(
+    2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name="drive"
+)
+baseshape.insertSegment(3, ramp, (high_level, 0), dur=ramp_time)
+baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name="wait")
+baseshape.setSegmentMarker("wait", (0, t_sine), 1)
+baseshape.setSegmentMarker("drive", (0, t_sine), 2)
+baseshape.setSR(SR)
+
+
+
+
+
[2]:
+
+
+
# Check that it looks the way we expect
+plotter(baseshape)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[3]:
+
+
+
# Make it into a sequence
+baseelem = bb.Element()
+baseelem.addBluePrint(1, baseshape)
+
+# Add elements and vary the sine freq.
+
+sine_freqs = np.linspace(1e9, 2e9, 5)
+
+seq = bb.Sequence()
+seq.setSR(SR)
+
+# We set each element of the sequence to wait for
+# a trigger from trigger input 'A'
+
+for index, freq in enumerate(sine_freqs):
+    elem = baseelem.copy()
+    elem.changeArg(1, "drive", "freq", freq)
+    seq.addElement(index + 1, elem)
+    seq.setSequencingTriggerWait(index + 1, 1)  # 1: trigA, 2: trigB, 3: EXT
+
+# and set the last element to point back to the first one
+seq.setSequencingGoto(index + 1, 1)
+
+seq.name = "tutorial_sequence"  # the sequence name will be needed later
+
+
+
+
+
+

Step 2: Initialise the driver

+
+
[4]:
+
+
+
# import and initialise the driver and ensure that the sample
+# rate and channel voltage is correct
+
+from qcodes.instrument_drivers.tektronix.AWG70002A import AWG70002A
+
+awg = AWG70002A("awg", "TCPIP0::172.20.2.243::inst0::INSTR")
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+ModuleNotFoundError                       Traceback (most recent call last)
+Cell In[4], line 4
+      1 # import and initialise the driver and ensure that the sample
+      2 # rate and channel voltage is correct
+----> 4 from qcodes.instrument_drivers.tektronix.AWG70002A import AWG70002A
+      6 awg = AWG70002A("awg", "TCPIP0::172.20.2.243::inst0::INSTR")
+
+ModuleNotFoundError: No module named 'qcodes'
+
+
+
+
[5]:
+
+
+
awg.sample_rate(SR)
+awg.ch1.awg_amplitude(0.5)  # this is the peak-to-peak amplitude of the channel
+
+# Now we must update the Sequence object with this information
+seq.setChannelAmplitude(1, awg.ch1.awg_amplitude())
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[5], line 1
+----> 1 awg.sample_rate(SR)
+      2 awg.ch1.awg_amplitude(0.5)  # this is the peak-to-peak amplitude of the channel
+      4 # Now we must update the Sequence object with this information
+
+NameError: name 'awg' is not defined
+
+
+
+
+

Step 3: Make output for the driver

+
+
[6]:
+
+
+
# If the sequence has been built correctly, it's a one-liner
+seqx_input = seq.outputForSEQXFile()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+KeyError                                  Traceback (most recent call last)
+Cell In[6], line 2
+      1 # If the sequence has been built correctly, it's a one-liner
+----> 2 seqx_input = seq.outputForSEQXFile()
+
+File /opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:988, in Sequence.outputForSEQXFile(self)
+    971 """
+    972 Generate a tuple matching the call signature of the QCoDeS
+    973 AWG70000A driver's `makeSEQXFile` function. If channel delays
+   (...)
+    984         go_to, wfms, amplitudes, seqname)
+    985 """
+    987 # most of the footwork is done by the following function
+--> 988 elements = self._prepareForOutputting()
+    989 # _prepareForOutputting asserts that channel amplitudes and
+    990 # full sequencing is specified
+    991 seqlen = len(elements)
+
+File /opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:882, in Sequence._prepareForOutputting(self)
+    880     ampkey = f"channel{chan}_amplitude"
+    881     if ampkey not in self._awgspecs.keys():
+--> 882         raise KeyError(
+    883             "No amplitude specified for channel {chan}. Can not continue."
+    884         )
+    886 # Apply channel delays.
+    887 delays = []
+
+KeyError: 'No amplitude specified for channel {chan}. Can not continue.'
+
+
+
+
+

Step 4: Build, send, load, and assign a .seqx file

+
+
[7]:
+
+
+
# compile a binary .seqx file
+seqx_output = awg.makeSEQXFile(*seqx_input)
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[7], line 2
+      1 # compile a binary .seqx file
+----> 2 seqx_output = awg.makeSEQXFile(*seqx_input)
+
+NameError: name 'awg' is not defined
+
+
+
+
[8]:
+
+
+
# transfer it to the awg harddrive
+awg.sendSEQXFile(seqx_output, "tutorial.seqx")
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[8], line 2
+      1 # transfer it to the awg harddrive
+----> 2 awg.sendSEQXFile(seqx_output, "tutorial.seqx")
+
+NameError: name 'awg' is not defined
+
+
+
+
[9]:
+
+
+
# load it into awg active memory
+awg.loadSEQXFile("tutorial.seqx")
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[9], line 2
+      1 # load it into awg active memory
+----> 2 awg.loadSEQXFile("tutorial.seqx")
+
+NameError: name 'awg' is not defined
+
+
+
+
[10]:
+
+
+
# assign tracks from the sequence to the awg sequencer
+awg.ch1.setSequenceTrack("tutorial_sequence", 1)
+
+# NB: Each channel has an assigned resolution, either 8, 9, or 10.
+# 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)
+# 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)
+# 10 means 10 bit for the waveform, no markers
+#
+# Since we want to have two markers, we make sure that the resolution is 8
+awg.ch1.resolution(8)
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[10], line 2
+      1 # assign tracks from the sequence to the awg sequencer
+----> 2 awg.ch1.setSequenceTrack("tutorial_sequence", 1)
+      4 # NB: Each channel has an assigned resolution, either 8, 9, or 10.
+      5 # 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)
+      6 # 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)
+      7 # 10 means 10 bit for the waveform, no markers
+      8 #
+      9 # Since we want to have two markers, we make sure that the resolution is 8
+     10 awg.ch1.resolution(8)
+
+NameError: name 'awg' is not defined
+
+
+
+
+

Step 5: Play it

+
+
[11]:
+
+
+
awg.ch1.state(1)
+awg.play()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[11], line 1
+----> 1 awg.ch1.state(1)
+      2 awg.play()
+
+NameError: name 'awg' is not defined
+
+
+
+
[12]:
+
+
+
# force trigger the instrument to play the next part of the sequence
+awg.force_triggerA()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[12], line 2
+      1 # force trigger the instrument to play the next part of the sequence
+----> 2 awg.force_triggerA()
+
+NameError: name 'awg' is not defined
+
+
+
+
[13]:
+
+
+
# eventually shut down and turn off
+awg.stop()
+awg.ch1.state(0)
+awg.close()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[13], line 2
+      1 # eventually shut down and turn off
+----> 2 awg.stop()
+      3 awg.ch1.state(0)
+      4 awg.close()
+
+NameError: name 'awg' is not defined
+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Making_output_for_Tektronix_AWG70000A.ipynb b/examples/Making_output_for_Tektronix_AWG70000A.ipynb new file mode 100644 index 000000000..e376db616 --- /dev/null +++ b/examples/Making_output_for_Tektronix_AWG70000A.ipynb @@ -0,0 +1,1495 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "The Tektronix AWG70000A series internally use `.seqx` files. The QCoDeS driver for the AWGs knows how to compile these files and broadbean knows what QCoDeS need to do so. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1: Make a sequence" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.027184Z", + "iopub.status.busy": "2025-01-20T06:27:54.027013Z", + "iopub.status.idle": "2025-01-20T06:27:54.408455Z", + "shell.execute_reply": "2025-01-20T06:27:54.407964Z" + } + }, + "outputs": [], + "source": [ + "# We make a sequence consisting of a bsic element with:\n", + "# a wait time, a ramp up, a sine, a ramp down, and more wait time\n", + "# And then we vary the sine frequency\n", + "%matplotlib notebook\n", + "import numpy as np\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "sine = bb.PulseAtoms.sine\n", + "ramp = bb.PulseAtoms.ramp\n", + "\n", + "##########################\n", + "# settings\n", + "\n", + "SR = 25e9\n", + "t1 = 10e-9 # first wait time (s)\n", + "ramp_time = 5e-9 # the ramp time (s)\n", + "t_sine = 25e-9 # the sine time (s)\n", + "high_level = 0.1 # the high level (V)\n", + "sine_amp = 0.05 # the sine amplitude (V)\n", + "t2 = 150e-9 # the second wait time (s)\n", + "f0 = 1e9 # the base frequency of the sine (Hz)\n", + "\n", + "baseshape = bb.BluePrint()\n", + "baseshape.insertSegment(0, ramp, (0, 0), dur=t1)\n", + "baseshape.insertSegment(1, ramp, (0, high_level), dur=ramp_time)\n", + "baseshape.insertSegment(\n", + " 2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name=\"drive\"\n", + ")\n", + "baseshape.insertSegment(3, ramp, (high_level, 0), dur=ramp_time)\n", + "baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name=\"wait\")\n", + "baseshape.setSegmentMarker(\"wait\", (0, t_sine), 1)\n", + "baseshape.setSegmentMarker(\"drive\", (0, t_sine), 2)\n", + "baseshape.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.410581Z", + "iopub.status.busy": "2025-01-20T06:27:54.410176Z", + "iopub.status.idle": "2025-01-20T06:27:54.433308Z", + "shell.execute_reply": "2025-01-20T06:27:54.432872Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Check that it looks the way we expect\n", + "plotter(baseshape)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.435031Z", + "iopub.status.busy": "2025-01-20T06:27:54.434677Z", + "iopub.status.idle": "2025-01-20T06:27:54.440133Z", + "shell.execute_reply": "2025-01-20T06:27:54.439560Z" + } + }, + "outputs": [], + "source": [ + "# Make it into a sequence\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, baseshape)\n", + "\n", + "# Add elements and vary the sine freq.\n", + "\n", + "sine_freqs = np.linspace(1e9, 2e9, 5)\n", + "\n", + "seq = bb.Sequence()\n", + "seq.setSR(SR)\n", + "\n", + "# We set each element of the sequence to wait for\n", + "# a trigger from trigger input 'A'\n", + "\n", + "for index, freq in enumerate(sine_freqs):\n", + " elem = baseelem.copy()\n", + " elem.changeArg(1, \"drive\", \"freq\", freq)\n", + " seq.addElement(index + 1, elem)\n", + " seq.setSequencingTriggerWait(index + 1, 1) # 1: trigA, 2: trigB, 3: EXT\n", + "\n", + "# and set the last element to point back to the first one\n", + "seq.setSequencingGoto(index + 1, 1)\n", + "\n", + "seq.name = \"tutorial_sequence\" # the sequence name will be needed later" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 2: Initialise the driver" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.441941Z", + "iopub.status.busy": "2025-01-20T06:27:54.441461Z", + "iopub.status.idle": "2025-01-20T06:27:54.587713Z", + "shell.execute_reply": "2025-01-20T06:27:54.587141Z" + } + }, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'qcodes'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[4], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# import and initialise the driver and ensure that the sample\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m# rate and channel voltage is correct\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mqcodes\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01minstrument_drivers\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mtektronix\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mAWG70002A\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m AWG70002A\n\u001b[1;32m 6\u001b[0m awg \u001b[38;5;241m=\u001b[39m AWG70002A(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mawg\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTCPIP0::172.20.2.243::inst0::INSTR\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'qcodes'" + ] + } + ], + "source": [ + "# import and initialise the driver and ensure that the sample\n", + "# rate and channel voltage is correct\n", + "\n", + "from qcodes.instrument_drivers.tektronix.AWG70002A import AWG70002A\n", + "\n", + "awg = AWG70002A(\"awg\", \"TCPIP0::172.20.2.243::inst0::INSTR\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.589548Z", + "iopub.status.busy": "2025-01-20T06:27:54.589217Z", + "iopub.status.idle": "2025-01-20T06:27:54.600596Z", + "shell.execute_reply": "2025-01-20T06:27:54.600168Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[5], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39msample_rate(SR)\n\u001b[1;32m 2\u001b[0m awg\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mawg_amplitude(\u001b[38;5;241m0.5\u001b[39m) \u001b[38;5;66;03m# this is the peak-to-peak amplitude of the channel\u001b[39;00m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# Now we must update the Sequence object with this information\u001b[39;00m\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "awg.sample_rate(SR)\n", + "awg.ch1.awg_amplitude(0.5) # this is the peak-to-peak amplitude of the channel\n", + "\n", + "# Now we must update the Sequence object with this information\n", + "seq.setChannelAmplitude(1, awg.ch1.awg_amplitude())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 3: Make output for the driver" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.602266Z", + "iopub.status.busy": "2025-01-20T06:27:54.601958Z", + "iopub.status.idle": "2025-01-20T06:27:54.676929Z", + "shell.execute_reply": "2025-01-20T06:27:54.676445Z" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'No amplitude specified for channel {chan}. Can not continue.'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[6], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# If the sequence has been built correctly, it's a one-liner\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m seqx_input \u001b[38;5;241m=\u001b[39m \u001b[43mseq\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutputForSEQXFile\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:988\u001b[0m, in \u001b[0;36mSequence.outputForSEQXFile\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 971\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 972\u001b[0m \u001b[38;5;124;03mGenerate a tuple matching the call signature of the QCoDeS\u001b[39;00m\n\u001b[1;32m 973\u001b[0m \u001b[38;5;124;03mAWG70000A driver's `makeSEQXFile` function. If channel delays\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 984\u001b[0m \u001b[38;5;124;03m go_to, wfms, amplitudes, seqname)\u001b[39;00m\n\u001b[1;32m 985\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 987\u001b[0m \u001b[38;5;66;03m# most of the footwork is done by the following function\u001b[39;00m\n\u001b[0;32m--> 988\u001b[0m elements \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_prepareForOutputting\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 989\u001b[0m \u001b[38;5;66;03m# _prepareForOutputting asserts that channel amplitudes and\u001b[39;00m\n\u001b[1;32m 990\u001b[0m \u001b[38;5;66;03m# full sequencing is specified\u001b[39;00m\n\u001b[1;32m 991\u001b[0m seqlen \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlen\u001b[39m(elements)\n", + "File \u001b[0;32m/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:882\u001b[0m, in \u001b[0;36mSequence._prepareForOutputting\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 880\u001b[0m ampkey \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mchannel\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mchan\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m_amplitude\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 881\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ampkey \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_awgspecs\u001b[38;5;241m.\u001b[39mkeys():\n\u001b[0;32m--> 882\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(\n\u001b[1;32m 883\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo amplitude specified for channel \u001b[39m\u001b[38;5;132;01m{chan}\u001b[39;00m\u001b[38;5;124m. Can not continue.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 884\u001b[0m )\n\u001b[1;32m 886\u001b[0m \u001b[38;5;66;03m# Apply channel delays.\u001b[39;00m\n\u001b[1;32m 887\u001b[0m delays \u001b[38;5;241m=\u001b[39m []\n", + "\u001b[0;31mKeyError\u001b[0m: 'No amplitude specified for channel {chan}. Can not continue.'" + ] + } + ], + "source": [ + "# If the sequence has been built correctly, it's a one-liner\n", + "seqx_input = seq.outputForSEQXFile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 4: Build, send, load, and assign a .seqx file" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.678421Z", + "iopub.status.busy": "2025-01-20T06:27:54.678258Z", + "iopub.status.idle": "2025-01-20T06:27:54.688667Z", + "shell.execute_reply": "2025-01-20T06:27:54.688211Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[7], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# compile a binary .seqx file\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m seqx_output \u001b[38;5;241m=\u001b[39m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mmakeSEQXFile(\u001b[38;5;241m*\u001b[39mseqx_input)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# compile a binary .seqx file\n", + "seqx_output = awg.makeSEQXFile(*seqx_input)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.690298Z", + "iopub.status.busy": "2025-01-20T06:27:54.689966Z", + "iopub.status.idle": "2025-01-20T06:27:54.700307Z", + "shell.execute_reply": "2025-01-20T06:27:54.699874Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[8], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# transfer it to the awg harddrive\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39msendSEQXFile(seqx_output, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtutorial.seqx\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# transfer it to the awg harddrive\n", + "awg.sendSEQXFile(seqx_output, \"tutorial.seqx\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.701963Z", + "iopub.status.busy": "2025-01-20T06:27:54.701557Z", + "iopub.status.idle": "2025-01-20T06:27:54.712222Z", + "shell.execute_reply": "2025-01-20T06:27:54.711611Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[9], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# load it into awg active memory\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mloadSEQXFile(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtutorial.seqx\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# load it into awg active memory\n", + "awg.loadSEQXFile(\"tutorial.seqx\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.713828Z", + "iopub.status.busy": "2025-01-20T06:27:54.713488Z", + "iopub.status.idle": "2025-01-20T06:27:54.760571Z", + "shell.execute_reply": "2025-01-20T06:27:54.760017Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[10], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# assign tracks from the sequence to the awg sequencer\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39msetSequenceTrack(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtutorial_sequence\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# NB: Each channel has an assigned resolution, either 8, 9, or 10.\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;66;03m# 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)\u001b[39;00m\n\u001b[1;32m 6\u001b[0m \u001b[38;5;66;03m# 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)\u001b[39;00m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;66;03m# 10 means 10 bit for the waveform, no markers\u001b[39;00m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;66;03m#\u001b[39;00m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;66;03m# Since we want to have two markers, we make sure that the resolution is 8\u001b[39;00m\n\u001b[1;32m 10\u001b[0m awg\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mresolution(\u001b[38;5;241m8\u001b[39m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# assign tracks from the sequence to the awg sequencer\n", + "awg.ch1.setSequenceTrack(\"tutorial_sequence\", 1)\n", + "\n", + "# NB: Each channel has an assigned resolution, either 8, 9, or 10.\n", + "# 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)\n", + "# 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)\n", + "# 10 means 10 bit for the waveform, no markers\n", + "#\n", + "# Since we want to have two markers, we make sure that the resolution is 8\n", + "awg.ch1.resolution(8)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 5: Play it" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.762342Z", + "iopub.status.busy": "2025-01-20T06:27:54.762026Z", + "iopub.status.idle": "2025-01-20T06:27:54.772943Z", + "shell.execute_reply": "2025-01-20T06:27:54.772383Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[11], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mstate(\u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 2\u001b[0m awg\u001b[38;5;241m.\u001b[39mplay()\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "awg.ch1.state(1)\n", + "awg.play()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.774727Z", + "iopub.status.busy": "2025-01-20T06:27:54.774420Z", + "iopub.status.idle": "2025-01-20T06:27:54.784696Z", + "shell.execute_reply": "2025-01-20T06:27:54.784120Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[12], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# force trigger the instrument to play the next part of the sequence\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mforce_triggerA()\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# force trigger the instrument to play the next part of the sequence\n", + "awg.force_triggerA()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-20T06:27:54.786353Z", + "iopub.status.busy": "2025-01-20T06:27:54.786041Z", + "iopub.status.idle": "2025-01-20T06:27:54.796352Z", + "shell.execute_reply": "2025-01-20T06:27:54.795927Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[13], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# eventually shut down and turn off\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mstop()\n\u001b[1;32m 3\u001b[0m awg\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mstate(\u001b[38;5;241m0\u001b[39m)\n\u001b[1;32m 4\u001b[0m awg\u001b[38;5;241m.\u001b[39mclose()\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# eventually shut down and turn off\n", + "awg.stop()\n", + "awg.ch1.state(0)\n", + "awg.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Pulse_Building_Tutorial.html b/examples/Pulse_Building_Tutorial.html new file mode 100644 index 000000000..5a384fe46 --- /dev/null +++ b/examples/Pulse_Building_Tutorial.html @@ -0,0 +1,1263 @@ + + + + + + + + + Pulsebuilding tutorial - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Pulsebuilding tutorial

+
+

Table of Contents

+ +
+
+

Lingo

+

Let’s settle on a vocabulary. At the highest level, we construct sequences. These sequences will eventually be uploaded to an AWG, e.g. the Tektronix AWG 5014. Each sequence consists of several elements than again consist of a number of channels. On each channel reside a waveform and two markers. The waveform and markers may either be added as numpy arrays or as blueprint. A blueprint is a set of instructions for making a waveform and two markers and consists of several +segments

+

That is to say, the food chain goes: segment -> blueprint -> element -> sequence.

+
+

Segments:

+
+

Intro to Normal segments:

+

A normal segment consists of a unique name, a function object, a tuple of arguments to the function, and a duration.

+
    +
  • The name: can be provided by the user or omitted. If omitted, the segment will get the name of its function. Since all names must be unique, the blueprint appends numbers to names if they occur more than once. The numbers are appended chronologically throughout the blueprint. See example below. Note that valid input (base) names are strings NOT ending in a number. Thus, ‘pi/2pulse’ is valid, whereas ‘pulsepi/2’ is not.

  • +
  • The function: must be a python function taking at least two arguments; the sample rate and the segment duration. If the function takes other arguments (such as ramp slope, frequency, etc.) sample rate and duration arguments must be the last positional arguments. Keyword arguments are currently not allowed. See example at the very end.

  • +
  • The arguments: are in a tuple of \(n-2\) arguments for a function taking \(n\) arguments, i.e. specifying everything but the sample rate and duration.

  • +
  • The duration is a single number giving the desired duration of the segment in seconds. Some responsibility for making this number sensible with respect to the sample rate rests on the user.

  • +
+
+
+

Intro to Special segments:

+

A special segment has a (protected) name and a number of arguments. So far, two special segments exist.

+
    +
  • waituntil, args [time (int)]: When put in a blueprint, this function ensures that the next segment starts at the absolute time time after the start of the element. It does so by filling any excess time with zeros. It fails if the previous segment will finish after time time.

  • +
  • makemeanfit. Not implemented yet. Will make the mean of the blueprint be a specified number. Will (eventually) exist in several versions, e.g. one achieving the goal by adding an offset, another by adding an appropriate DC segment at the end of the blueprint.

  • +
+
+
+
+

Intro to Blueprints:

+

Consist of a number of segments. Has an associated sample rate.

+
+
+

Intro to Elements:

+

Has a number of blueprints on each channel. Can have an arbitraty amount of integer-indexed channels, but the blueprint on each channel must have the same number of points and the same total duration as all the other blueprints.

+
+
+

Intro to Sequences:

+

Have an associated sample rate.

+
+
[1]:
+
+
+
#
+# IMPORTS
+#
+%matplotlib inline
+import matplotlib as mpl
+import numpy as np
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+mpl.rcParams["figure.figsize"] = (8, 3)
+mpl.rcParams["figure.subplot.bottom"] = 0.15
+
+
+
+
+
+
+
+

Blueprints

+
+

Basic blueprinting

+

(back to ToC)

+

In this section we show how to construct basic blueprints. The units of the vertical axis is volts and the units of the horizontal axis (the durations) is seconds.

+
+
[2]:
+
+
+
# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+sine = bb.PulseAtoms.sine  # args: freq, ampl, off, phase
+arb_func = bb.PulseAtoms.arb_func  # args provided in a dict
+
+# make a blueprint
+
+# The blueprint takes no arguments
+bp1 = bb.BluePrint()  # Do-nothing initialisation
+
+# the blueprint is filled via the insertSegment method
+# Call signature: position in the blueprint, function, args, name, duration
+bp1.insertSegment(0, ramp, (0, 1e-3), name="", dur=3e-6)
+
+# A sample rate can be set (Sa/S). Without a sample rate, we can not plot the blueprint.
+bp1.setSR(1e9)
+
+# The blueprint can be inspected. Note that the segment was auto-named 'ramp'
+bp1.showPrint()
+
+# more segments can be added...
+bp1.insertSegment(1, sine, (5e5, 1e-3, 1e-3, 0), name="mysine", dur=2e-6)
+bp1.insertSegment(2, ramp, (1e-3, 0), name="", dur=3e-6)
+bp1.insertSegment(
+    3, arb_func, (lambda t, ampl: ampl * t * t, {"ampl": 5e8}), name="myfunc", dur=2e-6
+)
+
+# ... and reinspected
+bp1.showPrint()
+
+
+
+
+
+
+
+
+Legend: Name, function, arguments, timesteps, durations
+Segment 1: "ramp", PulseAtoms.ramp, (0, 0.001), 3e-06
+----------
+Legend: Name, function, arguments, timesteps, durations
+Segment 1: "ramp", PulseAtoms.ramp, (0, 0.001), 3e-06
+Segment 2: "mysine", PulseAtoms.sine, (500000.0, 0.001, 0.001, 0), 2e-06
+Segment 3: "ramp2", PulseAtoms.ramp, (0.001, 0), 3e-06
+Segment 4: "myfunc", PulseAtoms.arb_func, (<function <lambda> at 0x7f59ac07bf60>, {'ampl': 500000000.0}), 2e-06
+----------
+
+
+
+
[3]:
+
+
+
# For easy overview, we may plot the blueprint
+plotter(bp1)
+
+# The two bleak lines (red, blue) above the graph represent the channel markers.
+# They are described below.
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_4_0.png +
+
+
+
[4]:
+
+
+
# Blueprints may be added together
+bp2 = bp1 + bp1
+plotter(bp2)
+
+# Segments may be removed from a blueprint. They are removed by name.
+bp2.removeSegment("ramp2")
+plotter(bp2)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_5_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_5_1.png +
+
+
+
[5]:
+
+
+
# A blueprint has a handful of different lengths one may check
+print(f"Number of points in blueprint: {bp1.points}")
+print(f"Length of blueprint in seconds: {bp1.duration}")
+print(f"Number of segments in blueprint: {bp1.length_segments}")
+
+
+
+
+
+
+
+
+Number of points in blueprint: 10000
+Length of blueprint in seconds: 9.999999999999999e-06
+Number of segments in blueprint: 4
+
+
+
+
+

Markers

+

(back to ToC)

+

All markers are OFF by default. Markers can be added to a blueprint (switched ON) in two different ways. Either a marker is specified by its ON time in absolute time or by its ON time relative to a certain segment.

+
+
[6]:
+
+
+
# Absolute time marker specification
+
+# The blueprint has a list of tuples for each marker. The tuples are (switch_on_time, duration)
+
+# create a blueprint
+bp_atm = bb.BluePrint()
+bp_atm.setSR(100)
+bp_atm.insertSegment(0, ramp, (0, 1), dur=3)
+bp_atm.insertSegment(1, sine, (0.5, 1, 1, 0), dur=2)
+bp_atm.insertSegment(2, ramp, (1, 0), dur=3)
+
+# specify markers in absolute time
+bp_atm.marker1 = [(1, 0.5), (2, 0.5)]
+bp_atm.marker2 = [(1.5, 0.2), (2.5, 0.1)]
+
+plotter(bp_atm)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_8_0.png +
+
+
+
[7]:
+
+
+
# Relative time marker specification
+
+bp_rtm = bb.BluePrint()
+bp_rtm.setSR(100)
+bp_rtm.insertSegment(0, ramp, (0, 1), dur=1)
+bp_rtm.insertSegment(1, ramp, (1, 1), dur=1)
+bp_rtm.insertSegment(
+    2, sine, (1.675, 1, 0, np.pi / 2), dur=1.5, name="mysine"
+)  # This is the important segment
+# make marker 1 go ON a bit before the sine comes on
+bp_rtm.setSegmentMarker(
+    "mysine", (-0.1, 0.2), 1
+)  # segment name, (delay, duration), markerID
+# make marker 2 go ON halfway through the sine
+bp_rtm.setSegmentMarker("mysine", (0.75, 0.1), 2)
+
+plotter(bp_rtm)
+
+# Even if we insert segments before and after the sine, the markers "stick" to the sine segment
+bp_rtm.insertSegment(0, ramp, (0, 0), dur=1)
+bp_rtm.insertSegment(-1, ramp, (0, 0.2), dur=1)
+
+plotter(bp_rtm)
+
+
+# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn
+# markers ON. It is up to the user to ensure that markers switch off again as expected, i.e. that different marker
+# specifications do not overlap.
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_9_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_9_1.png +
+
+
+
+

Modifying blueprints

+

(back to ToC)

+
+
[8]:
+
+
+
# An essential feature of blueprints is that they can be modified
+
+bp_mod = bb.BluePrint()
+bp_mod.setSR(100)
+
+bp_mod.insertSegment(0, ramp, (0, 0), name="before", dur=1)
+bp_mod.insertSegment(1, ramp, (1, 1), name="plateau", dur=1)
+bp_mod.insertSegment(2, ramp, (0, 0), name="after", dur=1)
+
+plotter(bp_mod)
+
+# Functional arguments can be changed
+
+# They are looked up by segment name
+bp_mod.changeArg(
+    "before", "stop", 1
+)  # the argument to change may either be the argument name or its position
+bp_mod.changeArg("after", 0, 1)
+
+plotter(bp_mod)
+
+# Durations can also be changed
+bp_mod.changeDuration("plateau", 2)
+
+plotter(bp_mod)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_11_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_11_1.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_11_2.png +
+
+
+
+

Special segments

+

(back to ToC)

+
+
[9]:
+
+
+
# The 'waituntil' segment fills up a part of the blueprint with zeros
+
+# Example: a square pulse, then waiting until 5 s exactly and then a new sine
+
+bp_wait = bb.BluePrint()
+bp_wait.setSR(100)
+
+bp_wait.insertSegment(0, ramp, (0, 0), dur=1)
+bp_wait.insertSegment(1, ramp, (1, 1), name="plateau", dur=1)
+# function must be sthe string 'waituntil', the argument is the ABSOLUTE time to wait until
+bp_wait.insertSegment(2, "waituntil", (5,))
+bp_wait.insertSegment(3, sine, (1, 0.1, 0, -np.pi / 2), dur=1)
+plotter(bp_wait)
+
+# If we make the square pulse longer, the sine still occurs at 5 s
+bp_wait.changeDuration("plateau", 1.5)
+plotter(bp_wait)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_13_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_13_1.png +
+
+
+
+
+

Elements

+

(back to ToC)

+

Elements are containers containing blueprints. A valid element consists of blueprints that all have the same number of points and the same overall duration.

+
+
[10]:
+
+
+
# Example 1
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=0.1e-6)
+bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name="top", dur=0.1e-6)
+bp_square.insertSegment(2, ramp, (0, 0), dur=0.1e-6)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 25e-3, 0, 0), dur=0.3e-6)
+bp_sineandboxes = bp_sine + bp_square
+
+# Now we create an element and add the blueprints to channel 1 and 3, respectively
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+
+# We can check the validity of the element
+elem1.validateDurations()  # raises an ElementDurationError if something is wrong. If all is OK, does nothing.
+
+# And we can plot the element
+plotter(elem1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_15_0.png +
+
+
+
[11]:
+
+
+
# An element has several features
+print(f"Designated channels: {elem1.channels}")
+print(f"Total duration: {elem1.duration} s")
+print(f"Sample rate: {elem1.SR} (Sa/S)")
+
+
+
+
+
+
+
+
+Designated channels: [1, 3]
+Total duration: 6e-07 s
+Sample rate: 1000000000.0 (Sa/S)
+
+
+
+
[12]:
+
+
+
# We can modify the blueprints of an element through the element object
+
+# Change the sine freq
+elem1.changeArg(
+    3, "sine", "freq", 6.67e6
+)  # Call signature: channel, segment name, argument, new_value
+
+# make the second plateaus last longer
+elem1.changeDuration(
+    1, "top2", 0.2e-6
+)  # In this blueprint, the second top is called top2
+elem1.changeDuration(3, "top", 0.2e-6)
+
+plotter(elem1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_17_0.png +
+
+
+
+

Sequences

+

(back to ToC)

+

Finally, we have reached the top level of the module: sequences. Unsurprisingly, sequences are containers containing elements. All elements in a sequence must specify the same channels.

+
+
[13]:
+
+
+
seq1 = bb.Sequence()
+
+# We fill up the sequence by adding elements at different sequence positions.
+# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3
+
+#
+# Make blueprints, make elements
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)
+bp_sineandboxes = bp_sine + bp_square
+
+# create elements
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+#
+elem2 = bb.Element()
+elem2.addBluePrint(3, bp_boxes)
+elem2.addBluePrint(1, bp_sineandboxes)
+
+# Fill up the sequence
+seq1.addElement(1, elem1)  # Call signature: seq. pos., element
+seq1.addElement(2, elem2)
+seq1.addElement(3, elem1)
+
+# set its sample rate
+seq1.setSR(elem1.SR)
+
+
+
+
+
[14]:
+
+
+
help(seq1.element(1).changeArg)
+
+
+
+
+
+
+
+
+Help on method changeArg in module broadbean.element:
+
+changeArg(channel: 'str | int', name: 'str', arg: 'str | int', value: 'int | float', replaceeverywhere: 'bool' = False) -> 'None' method of broadbean.element.Element instance
+    Change the argument of a function of the blueprint on the specified
+    channel.
+
+    Args:
+        channel: The channel where the blueprint sits.
+        name: The name of the segment in which to change an argument
+        arg: Either the position (int) or name (str) of
+            the argument to change
+        value: The new value of the argument
+        replaceeverywhere: If True, the same argument is overwritten
+            in ALL segments where the name matches. E.g. 'gaussian1' will
+            match 'gaussian', 'gaussian2', etc. If False, only the segment
+            with exact name match gets a replacement.
+
+    Raises:
+        ValueError: If the specified channel has no blueprint.
+        ValueError: If the argument can not be matched (either the argument
+            name does not match or the argument number is wrong).
+
+
+
+
+
[15]:
+
+
+
# The sequence can be validated
+seq1.checkConsistency()  # returns True if all is well, raises errors if not
+
+# And the sequence can (if valid) be plotted
+plotter(seq1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_21_0.png +
+
+
+

Tektronix AWG 5014 output

+

(back to ToC)

+

The sequence object can output a tuple matching the call signature of the QCoDeS Tektronix AWG 5014 driver.

+

For the translation from voltage to AWG unsigned integer format to be correct, the voltage ranges and offsets of the AWG channels must be specified (NB: This will NOT work if the channels on the AWG are in high/low mode).

+

Furthermore, the AWG sequencer options should be specified for each sequence element.

+
+
[16]:
+
+
+
seq1.setChannelAmplitude(1, 10e-3)  # Call signature: channel, amplitude (peak-to-peak)
+seq1.setChannelOffset(1, 0)
+seq1.setChannelAmplitude(3, 10e-3)
+seq1.setChannelOffset(3, 0)
+
+# Here we repeat each element twice and then proceed to the next, wrapping over at the end
+seq1.setSequencingTriggerWait(1, 0)
+seq1.setSequencingNumberOfRepetitions(1, 2)
+seq1.setSequencingEventJumpTarget(1, 0)
+seq1.setSequencingGoto(1, 2)
+#
+seq1.setSequencingTriggerWait(2, 0)
+seq1.setSequencingNumberOfRepetitions(2, 2)
+seq1.setSequencingEventJumpTarget(2, 0)
+seq1.setSequencingGoto(1, 3)
+#
+seq1.setSequencingTriggerWait(3, 0)
+seq1.setSequencingNumberOfRepetitions(3, 2)
+seq1.setSequencingEventJumpTarget(3, 0)
+seq1.setSequencingGoto(3, 1)
+
+# then we may finally get the "package" to give the QCoDeS driver for upload
+package = seq1.outputForAWGFile()
+
+# Note that the sequencing information is included in the plot in a way mimicking the
+# way the display of the Tektronix AWG 5014
+plotter(seq1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_23_0.png +
+
+
+
[17]:
+
+
+
# The package is a SLICEABLE object
+# By slicing and indexing it, one may retrieve different parts of the sequence
+
+chan1_awg_input = package[0]  # returns a tuple yielding an awg file with channel 1
+chan3_awg_input = package[1]  # returns a tuple yielding an awg file with channel 3
+
+both_chans_awg_input = package[
+    :
+]  # returns a tuple yielding an awg file with both channels
+
+# This may be useful to make one big sequence for one experiment and then uploading part of it to one awg
+# and part of it to another (since physical awg's usually don't have enough channels for a big experiment)
+
+# To see how the channels are counted, look up the channels
+package.channels
+
+
+
+
+
[17]:
+
+
+
+
+[1, 3]
+
+
+
+
[18]:
+
+
+
## Example of uploading the sequence (requires having qcodes installed, see https://github.com/QCoDeS/Qcodes)
+
+# from qcodes.instrument_drivers.tektronix.AWG5014 import Tektronix_AWG5014
+# awg = Tektronix_AWG5014('AWG1', 'TCPIP0::172.20.3.57::inst0::INSTR', timeout=40)
+# awg.make_send_and_load_awg_file(*package[:])
+
+
+
+
+

Delays and filter compensation

+

(back to ToC)

+

In a real experimental setting, the signal transmission line may distort and/or delay the pulse sequence. The Sequence object can perform some compensation for this when making the .awg file.

+
+
[19]:
+
+
+
# To delay channel 1 with respect to the other channels, set its delay
+seq1.setChannelDelay(1, 0)
+seq1.setChannelDelay(3, 123e-9)
+
+# To apply for a high pass filter with a cut-off frequency of 1 MHz on channel 3, we can do
+seq1.setChannelFilterCompensation(3, "HP", order=1, f_cut=1e6)
+# or, equivalently,
+seq1.setChannelFilterCompensation(3, "HP", order=1, tau=1e-6)
+
+# Note that setting the filter compensation may invalidate the sequence in the sense that the specified voltage ranges
+# on the AWG may have become too small. The function outputForAWGFile will warn you if this is the case.
+
+newpackage = seq1.outputForAWGFile()
+
+
+
+
+
[20]:
+
+
+
# For sanity checking, it may be helpful to see how the compensated waveforms look.
+# The plotter function can display the delays and filter compensations
+
+plotter(seq1, apply_filters=True, apply_delays=True)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_28_0.png +
+
+
+
+
+

Sequences varying parameters

+

(back to ToC)

+

The module contains a few wrapper functions to easily generate sequences with some parameter(s) varying throughout the sequence. First two examples where a Base element is provided and varied, then an example where an existing Sequence is repeated.

+
+
[21]:
+
+
+
# Example 1: vary the duration of a square pulse
+
+# First, we make a basic element, in this example containing just a single blueprint
+basebp = bb.BluePrint()
+basebp.insertSegment(0, ramp, (0, 0), dur=0.5)
+basebp.insertSegment(1, ramp, (1, 1), dur=1, name="varyme")
+basebp.insertSegment(2, "waituntil", 5)
+basebp.setSR(100)
+
+baseelem = bb.Element()
+baseelem.addBluePrint(1, basebp)
+
+plotter(baseelem)
+
+# Now we make a 5-step sequence varying the duration of the high level
+# The inputs are lists, since several things can be varied (see Example 2)
+channels = [1]
+names = ["varyme"]
+args = ["duration"]
+iters = [[1, 1.5, 2, 2.5, 3]]
+
+seq1 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)
+plotter(seq1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_30_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_30_1.png +
+
+
+
[22]:
+
+
+
# Example 2: Vary the duration AND the high level
+
+# It is straighforward to vary several things throughout the sequence
+
+# We make the same base element as in Example 1
+basebp = bb.BluePrint()
+basebp.insertSegment(0, ramp, (0, 0), dur=0.5)
+basebp.insertSegment(1, ramp, (1, 1), dur=1, name="varyme")
+basebp.insertSegment(2, "waituntil", 5)
+basebp.setSR(100)
+
+baseelem = bb.Element()
+baseelem.addBluePrint(1, basebp)
+
+# Now we make a 5-step sequence varying the duration AND the high level
+# We thus vary 3 things, a duration, a ramp start, and a ramp stop
+channels = [1, 1, 1]
+names = ["varyme", "varyme", "varyme"]
+args = ["duration", "start", "stop"]
+iters = [[1, 1.5, 2, 2.5, 3], [1, 0.8, 0.7, 0.6, 0.5], [1, 0.8, 0.7, 0.6, 0.5]]
+
+seq2 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)
+plotter(seq2)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_31_0.png +
+
+
+
[23]:
+
+
+
# Example 3: Modify the high level of a square pulse inside a sequence
+
+#
+
+pulsebp = bb.BluePrint()
+pulsebp.insertSegment(0, ramp, (0, 0), dur=5e-4)
+pulsebp.insertSegment(1, ramp, (1, 1), dur=1e-3, name="varyme")
+pulsebp.insertSegment(2, "waituntil", 2e-3)
+pulsebp.setSR(1e6)
+
+sinebp = bb.BluePrint()
+sinebp.insertSegment(0, sine, (0.2e3, 0.5, 0.5, 0), dur=10e-3)
+sinebp.setSR(1e6)
+
+elem1 = bb.Element()
+elem1.addBluePrint(1, pulsebp)
+
+elem2 = bb.Element()
+elem2.addBluePrint(1, sinebp)
+
+baseseq = bb.Sequence()
+baseseq.setSR(1e6)
+baseseq.addElement(1, elem1)
+baseseq.addElement(2, elem2)
+
+baseseq.setSequenceSettings(1, 0, 20, 0, 0)
+baseseq.setSequenceSettings(2, 0, 1, 0, 1)
+
+plotter(baseseq)
+
+# now vary this sequence
+
+poss = [1, 1]
+channels = [1, 1]
+names = ["varyme", "varyme"]
+args = ["start", "stop"]
+iters = [[1, 0.75, 0.5], [1, 0.75, 0.5]]
+
+newseq = bb.repeatAndVarySequence(baseseq, poss, channels, names, args, iters)
+plotter(newseq)
+
+
+
+
+
+
+
+
+/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:190: UserWarning: Deprecation warning. This function is only compatible with AWG5014 output and will be removed. Please use the specific setSequencingXXX methods.
+  warnings.warn(
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_32_1.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_32_2.png +
+
+
+
[ ]:
+
+
+

+
+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Pulse_Building_Tutorial.ipynb b/examples/Pulse_Building_Tutorial.ipynb new file mode 100644 index 000000000..21cbefc09 --- /dev/null +++ b/examples/Pulse_Building_Tutorial.ipynb @@ -0,0 +1,1223 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pulsebuilding tutorial\n", + "\n", + "## Table of Contents\n", + "\n", + " * [Lingo](#Lingo)\n", + " * [Blueprints](#Blueprints)\n", + " * [Basic blueprinting](#Basic-blueprinting)\n", + " * [Markers](#Markers)\n", + " * [Modifying blueprints](#Modifying-blueprints)\n", + " * [Special segments](#special-segments)\n", + "\n", + " * [Elements](#elements)\n", + " * [Sequences](#sequences)\n", + " * [Tektronix AWG 5014 output](#Tektronix-AWG-5014-output)\n", + " * [Delays and filter compensation](#Delays-and-filter-compensation)\n", + " * [Sequences varying parameters](#Sequences-varying-parameters)\n", + "\n", + "## Lingo\n", + "\n", + "Let's settle on a vocabulary. At the highest level, we construct **sequences**. These sequences will eventually be uploaded to an AWG, e.g. the Tektronix AWG 5014. Each sequence consists of several **elements** than again consist of a number of **channels**. On each channel reside a **waveform** and two **markers**. The waveform and markers may either be added as numpy arrays or as **blueprint**. A blueprint is a set of instructions for making a waveform and two markers and consists of several **segments**\n", + "\n", + "That is to say, the food chain goes: segment -> blueprint -> element -> sequence.\n", + "\n", + "\n", + "### Segments:\n", + "\n", + "#### Intro to Normal segments:\n", + "\n", + "A normal segment consists of a _unique_ name, a function object, a tuple of arguments to the function, and a **duration**. \n", + "\n", + " * The name: can be provided by the user or omitted. If omitted, the segment will get the name of its function. Since all names must be unique, the blueprint _appends numbers_ to names if they occur more than once. The numbers are appended chronologically throughout the blueprint. See example below. Note that valid input (base) names are strings NOT ending in a number. Thus, 'pi/2pulse' is valid, whereas 'pulsepi/2' is not.\n", + " \n", + " * The function: must be a python function taking at least two arguments; the sample rate and the segment duration. If the function takes other arguments (such as ramp slope, frequency, etc.) sample rate and duration arguments must be the last positional arguments. Keyword arguments are currently not allowed. See example at the very end.\n", + " \n", + " * The arguments: are in a tuple of $n-2$ arguments for a function taking $n$ arguments, i.e. specifying everything but the sample rate and duration.\n", + " \n", + "* The duration is a single number giving the desired duration of the segment in seconds. Some responsibility for making this number sensible with respect to the sample rate rests on the user.\n", + "\n", + "#### Intro to Special segments:\n", + "\n", + "A special segment has a (protected) name and a number of arguments. So far, two special segments exist.\n", + " \n", + " * `waituntil`, args [time (int)]: When put in a blueprint, this function ensures that the _next_ segment starts at the absolute time `time` after the start of the element. It does so by filling any excess time with zeros. It fails if the previous segment will finish after time `time`.\n", + " \n", + " * `makemeanfit`. Not implemented yet. Will make the mean of the blueprint be a specified number. Will (eventually) exist in several versions, e.g. one achieving the goal by adding an offset, another by adding an appropriate DC segment at the end of the blueprint.\n", + "\n", + "### Intro to Blueprints:\n", + "\n", + "Consist of a number of segments. Has an associated sample rate.\n", + "\n", + "### Intro to Elements:\n", + "\n", + "Has a number of blueprints on each channel. Can have an arbitraty amount of integer-indexed channels, but the blueprint on each channel must have the same number of points and the same total duration as all the other blueprints.\n", + "\n", + "### Intro to Sequences:\n", + "\n", + "Have an associated sample rate. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:56.284720Z", + "iopub.status.busy": "2025-01-20T06:27:56.284305Z", + "iopub.status.idle": "2025-01-20T06:27:56.665089Z", + "shell.execute_reply": "2025-01-20T06:27:56.664539Z" + } + }, + "outputs": [], + "source": [ + "#\n", + "# IMPORTS\n", + "#\n", + "%matplotlib inline\n", + "import matplotlib as mpl\n", + "import numpy as np\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Blueprints\n", + "\n", + "## Basic blueprinting\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "In this section we show how to construct basic blueprints. The units of the vertical axis is **volts** and the units of the horizontal axis (the durations) is **seconds**." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:56.667398Z", + "iopub.status.busy": "2025-01-20T06:27:56.666988Z", + "iopub.status.idle": "2025-01-20T06:27:56.672694Z", + "shell.execute_reply": "2025-01-20T06:27:56.672126Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Legend: Name, function, arguments, timesteps, durations\n", + "Segment 1: \"ramp\", PulseAtoms.ramp, (0, 0.001), 3e-06\n", + "----------\n", + "Legend: Name, function, arguments, timesteps, durations\n", + "Segment 1: \"ramp\", PulseAtoms.ramp, (0, 0.001), 3e-06\n", + "Segment 2: \"mysine\", PulseAtoms.sine, (500000.0, 0.001, 0.001, 0), 2e-06\n", + "Segment 3: \"ramp2\", PulseAtoms.ramp, (0.001, 0), 3e-06\n", + "Segment 4: \"myfunc\", PulseAtoms.arb_func, ( at 0x7f59ac07bf60>, {'ampl': 500000000.0}), 2e-06\n", + "----------\n" + ] + } + ], + "source": [ + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "arb_func = bb.PulseAtoms.arb_func # args provided in a dict\n", + "\n", + "# make a blueprint\n", + "\n", + "# The blueprint takes no arguments\n", + "bp1 = bb.BluePrint() # Do-nothing initialisation\n", + "\n", + "# the blueprint is filled via the insertSegment method\n", + "# Call signature: position in the blueprint, function, args, name, duration\n", + "bp1.insertSegment(0, ramp, (0, 1e-3), name=\"\", dur=3e-6)\n", + "\n", + "# A sample rate can be set (Sa/S). Without a sample rate, we can not plot the blueprint.\n", + "bp1.setSR(1e9)\n", + "\n", + "# The blueprint can be inspected. Note that the segment was auto-named 'ramp'\n", + "bp1.showPrint()\n", + "\n", + "# more segments can be added...\n", + "bp1.insertSegment(1, sine, (5e5, 1e-3, 1e-3, 0), name=\"mysine\", dur=2e-6)\n", + "bp1.insertSegment(2, ramp, (1e-3, 0), name=\"\", dur=3e-6)\n", + "bp1.insertSegment(\n", + " 3, arb_func, (lambda t, ampl: ampl * t * t, {\"ampl\": 5e8}), name=\"myfunc\", dur=2e-6\n", + ")\n", + "\n", + "# ... and reinspected\n", + "bp1.showPrint()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:56.674392Z", + "iopub.status.busy": "2025-01-20T06:27:56.674081Z", + "iopub.status.idle": "2025-01-20T06:27:56.751399Z", + "shell.execute_reply": "2025-01-20T06:27:56.750844Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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w8P4m7/5G3z6059WOToiand33nknfvjDH+KZZ55ROxQi6kA6nQ4PPPAA3vGOdwDYGC0dGBjAxz72MXz84x8HAGQyGfT29uLkyZN417vepUqcHDHdh2q1iqeeegrXXHPN1jm9Xo9rrrkGjz/+uIqREVEnmZqawsDAAMbGxvCe97wH8/PzaodERB1qdnYWq6urktzG4/Hg6quvVjW3YWK6D/F4HI1GA729vZLzvb29WF1dVSkqIuokV199NU6ePImHHnoI9913H2ZnZ/H6178euVxO7dCIqANt5i9ay22Mqt0zERFtuf7667f+f8kll+Dqq69GJBLB97//fdx8880qRkZE1DocMd2HYDAIg8GAtbU1yfm1tTX09fWpFBURdTKv14tjx45henpa7VCIqANt5i9ay22YmO6D2WzG5ZdfjocffnjrnCAIePjhh/HqV79axciIqFPl83nMzMygv79f7VCIqAONjo6ir69Pkttks1n85je/UTW34VT+Pt1666246aabcMUVV+Cqq67CV77yFRQKBbz//e9XOzQi6gAf//jH8fa3vx2RSATLy8u44447YDAY8O53v1vt0IioTeXzecmsy+zsLJ555hn4/X6Ew2F89KMfxec+9zlMTk5idHQUf//3f4+BgYGtnftqYGK6T+985zsRi8Vw++23Y3V1FZdeeikeeughxaJhIqKDWFxcxLvf/W4kEgmEQiG87nWvw69//WuEQiG1QyOiNvXkk0/izW9+89bxrbfeCgC46aabcPLkSXzyk59EoVDAX/7lXyKdTuN1r3sdHnroIVitVrVCZh1TIiIiItIGrjElIiIiIk1gYkpEREREmsDElIiIiIg0gYkpEREREWkCE1MiIiIi0gQmpkRERESkCUxMiYiIiEgTmJgSERERkSYwMSUi2kMikUBPTw/m5uYOdTtvetOb8NGPfrQpMTXLu971LnzpS19SOwwioi3s/EREtIdbb70VuVwO999//6FuJ5lMwmQyweVyNSmywzt16hTe8IY3YHZ2Fh6PR+1wiIg4YkpEtJtisYhvfOMbuPnmmw99W36//8BJqSiKqNfrh45B7qKLLsL4+Di+9a1vNf22iYgOgokpEdEufvzjH8NiseAP/uAPts7993//N3Q6HX7605/isssug81mwx/+4R9ifX0dP/nJT3D8+HG43W78xV/8BYrF4tb3yafyK5UKPvWpT2F4eBgWiwUTExP4xje+IbmPn/zkJ7j88sthsVjwq1/9CpVKBR/5yEfQ09MDq9WK173udXjiiSf2/Bn++Z//GZOTk7Barejt7cUNN9wg+frb3/52fPe7323Cb4uI6PCMagdARKRVjz32GC6//PIdv3bnnXfia1/7Gux2O2688UbceOONsFgs+Pa3v418Po8/+7M/wz333INPfepTO37/+973Pjz++OP46le/ihMnTmB2dhbxeFxyzac//Wn80z/9E8bGxuDz+fDJT34SP/jBD/Av//IviEQiuPvuu3Hddddhenoafr9fcR9PPvkkPvKRj+Bf//Vf8ZrXvAbJZBKPPfaY5JqrrroKd911FyqVCiwWywF/U0REzcHElIhoF9FoFAMDAzt+7XOf+xxe+9rXAgBuvvlm3HbbbZiZmcHY2BgA4IYbbsAvf/nLHRPTl156Cd///vfx85//HNdccw0AbH3fdv/wD/+Aa6+9FgBQKBRw33334eTJk7j++usBAPfffz9+/vOf4xvf+AY+8YlPKL5/fn4eDocDb3vb2+ByuRCJRHDZZZdJrhkYGEC1WsXq6ioikch+fzVEREeCU/lERLsolUqwWq07fu2SSy7Z+n9vby/sdrskuezt7cX6+vqO3/vMM8/AYDDgjW984573f8UVV2z9f2ZmBrVabSsZBgCTyYSrrroKzz///I7ff+211yISiWBsbAzvfe978W//9m+S5QUAYLPZAEBxnohIDUxMiYh2EQwGkUqldvyayWTa+r9Op5Mcb54TBGHH791MBl+Jw+HYZ6Q7c7lcePrpp/Gd73wH/f39uP3223HixAmk0+mta5LJJAAgFAod6r6IiJqBiSkR0S4uu+wynD59uum3e/HFF0MQBDzyyCP7/p7x8XGYzWb8z//8z9a5Wq2GJ554AhdccMGu32c0GnHNNdfg7rvvxnPPPYe5uTn84he/2Pr6qVOnMDQ0hGAweLAfhoioibjGlIhoF9dddx1uu+02pFIp+Hy+pt3uyMgIbrrpJnzgAx/Y2vwUjUaxvr6OG2+8ccfvcTgc+Ku/+it84hOfgN/vRzgcxt13341isbhrOasf/ehHOHPmDN7whjfA5/Phxz/+MQRBwHnnnbd1zWOPPYa3vOUtTfvZiIgOg4kpEdEuLr74YrzqVa/C97//fXzoQx9q6m3fd999+MxnPoO//uu/RiKRQDgcxmc+85k9v+cLX/gCBEHAe9/7XuRyOVxxxRX46U9/umvS7PV68R//8R+48847US6XMTk5ie985zu48MILAQDlchk//OEP8dBDDzX1ZyMiOih2fiIi2sODDz6IT3ziEzh16hT0+s5a/XTffffhgQcewM9+9jO1QyEiAsARUyKiPf3Jn/wJpqamsLS0hOHhYbXDaSqTyYR77rlH7TCIiLZwxJSIiIiINKGz5qWIiIiIqG0xMSUiIiIiTWBiSkRERESawMSUiIiIiDSBiSkRERERaQITUyIiIiLSBCamRERERKQJTEyJiIiISBOYmBIRERGRJvx/o2Sz2N23KekAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For easy overview, we may plot the blueprint\n", + "plotter(bp1)\n", + "\n", + "# The two bleak lines (red, blue) above the graph represent the channel markers.\n", + "# They are described below." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:56.753319Z", + "iopub.status.busy": "2025-01-20T06:27:56.753004Z", + "iopub.status.idle": "2025-01-20T06:27:56.912631Z", + "shell.execute_reply": "2025-01-20T06:27:56.912066Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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1NTUlKWsw1JB/8HBQdTW8tQHIio99jNEZLVy9zO6rmN+fh6dnZ1QbnM4katnTBVFQXd3+c+qra2t5YBBRJZiP0NEVitnP3O7mdIlTH4iIiIiIkdgYEpEREREjsDAdB3C4TA+/OEPIxwO290UIvIo9jNEZDUn9zOuTn4iIiIiIu/gjCkREREROQIDUyIiIiJyBAamREREROQIDEyJiIiIyBEYmK7DU089hc2bNyMSieDuu+/GD3/4Q7ubREQu9f3vfx9ve9vb0NnZCZ/Ph3/7t38zfF/XdXzoQx9CR0cHotEo7r33Xly+fNmexhKR6zz++OM4evQoampq0Nraire//e24ePGi4TGpVAoPP/wwmpqaUF1djV/8xV/E2NiYTS3OY2C6Rl/84hfx6KOP4sMf/jBOnjyJAwcO4L777sP4+LjdTSMiF0okEjhw4ACeeuop0+8/8cQTePLJJ/HZz34W//d//4dYLIb77rsPqVSqzC0lIjd6/vnn8fDDD+Oll17Ct771LWSzWbz5zW9GIpFYfszv/d7v4T//8z/xz//8z3j++ecxPDyMX/iFX7Cx1SwXtWZ33303jh49ik996lMAAE3T0NPTg/e85z34wz/8Q5tbR0Ru5vP58Nxzz+Htb387gPxsaWdnJ973vvfh93//9wEAc3NzaGtrwzPPPIN3vOMdNraWiNxoYmICra2teP755/H6178ec3NzaGlpwbPPPov7778fAHDhwgXs3r0bL774Il7zmtfY0k7OmK5BJpPBiRMncO+99y5fUxQF9957L1588UUbW0ZEXnTt2jWMjo4a+py6ujrcfffd7HOIqChzc3MAgMbGRgDAiRMnkM1mDf3Mrl270Nvba2s/w8B0DSYnJ6GqKtra2gzX29raMDo6alOriMirlvoV9jlEVAqapuG9730vXve612Hfvn0A8v1MKBRCfX294bF29zMB234yEREREVnu4YcfRn9/P37wgx/Y3ZTb4ozpGjQ3N8Pv90uZamNjY2hvb7epVUTkVUv9CvscItqoRx55BF/5ylfw3e9+F93d3cvX29vbkclkMDs7a3i83f0MA9M1CIVCOHz4ML797W8vX9M0Dd/+9rdxzz332NgyIvKiLVu2oL293dDnxONx/N///R/7HCJaE13X8cgjj+C5557Dd77zHWzZssXw/cOHDyMYDBr6mYsXL2JgYMDW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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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1NTUSH/jT3/6Ez70oQ/hP/7jP/DKV74S4+PjeOqppwyvOXToEL74xS8ilUrB7/fnuaeIiAqDgSkR0RJ6enrQ3Nyc87kvfOELeNWrXgUAeO9734v77rsPly5dwqZNmwAAt99+O5544omcgen58+fxox/9CL/5zW9w0003AcDC+xb753/+Z9x8880AgNnZWTz00EN49NFHcdtttwEAHnnkEfzmN7/Bt771Ldx7773S+3t7exEOh/GmN70JkUgEHR0dOHDggOE1zc3NSKfTGBoaQkdHx2p3DRFRUXAon4hoCYlEAoFAIOdz+/btW/jvhoYGhEIhQ3DZ0NCAkZGRnO89fvw43G43Xvva1y7796+//vqF/7506RIymcxCMAwAXq8Xhw4dwtmzZ3O+/+abb0ZHRwc2bdqEd77znfjP//xPQ3oBAASDQQCQthMRmYGBKRHREmprazExMZHzOa/Xu/DfiqIYHs9v0zQt53vng8GVhMPhVbY0t0gkgueeew7f//730dTUhM985jPo6urC5OTkwmvGx8cBAHV1dev6W0REhcDAlIhoCQcOHMCZM2cK/rl79+6Fpml48sknV/2ezZs3w+fz4Y9//OPCtkwmg2effRa7du1a8n0ejwc33XQT7r//fpw8eRLd3d347W9/u/D8qVOn0Nraitra2vz+Z4iICog5pkRES7jllltw3333YWJiAtXV1QX73M7OTtx55514z3veszD5qaenByMjI7jjjjtyviccDuMDH/gA7r33XtTU1KC9vR33338/4vH4kuWsfvazn+Gll17Ca17zGlRXV+MXv/gFNE3D9u3bF17z1FNP4Q1veEPB/t+IiNaDgSkR0RL27t2L6667Dj/60Y/w/ve/v6Cf/dBDD+GTn/wk/vEf/xFjY2Nob2/HJz/5yWXf8+UvfxmapuGd73wnZmZmcP311+NXv/rVkkFzVVUV/vu//xuf+9znkEwmsXXrVnz/+9/H7t27AQDJZBI//elP8dhjjxX0/42IKF9c+YmIaBk///nPce+99+LUqVNwuZyV/fTQQw/hJz/5CX7961+b3RQiIgDsMSUiWtZf/uVf4sKFC+jv70dbW5vZzSkor9eLBx54wOxmEBEtYI8pEREREVmCs8aliIiIiMi2GJgSERERkSUwMCUiIiIiS2BgSkRERESWwMCUiIiIiCyBgSkRERERWQIDUyIiIiKyBAamRERERGQJDEyJiIiIyBL+P1QSAyZxtb0QAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Blueprints may be added together\n", + "bp2 = bp1 + bp1\n", + "plotter(bp2)\n", + "\n", + "# Segments may be removed from a blueprint. They are removed by name.\n", + "bp2.removeSegment(\"ramp2\")\n", + "plotter(bp2)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:56.914485Z", + "iopub.status.busy": "2025-01-20T06:27:56.914158Z", + "iopub.status.idle": "2025-01-20T06:27:56.917690Z", + "shell.execute_reply": "2025-01-20T06:27:56.917225Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of points in blueprint: 10000\n", + "Length of blueprint in seconds: 9.999999999999999e-06\n", + "Number of segments in blueprint: 4\n" + ] + } + ], + "source": [ + "# A blueprint has a handful of different lengths one may check\n", + "print(f\"Number of points in blueprint: {bp1.points}\")\n", + "print(f\"Length of blueprint in seconds: {bp1.duration}\")\n", + "print(f\"Number of segments in blueprint: {bp1.length_segments}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Markers\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "All markers are OFF by default. Markers can be added to a blueprint (switched ON) in two different ways. Either a marker is specified by its ON time in *absolute time* or by its ON time *relative* to a certain segment." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:56.919359Z", + "iopub.status.busy": "2025-01-20T06:27:56.919033Z", + "iopub.status.idle": "2025-01-20T06:27:56.982234Z", + "shell.execute_reply": "2025-01-20T06:27:56.981665Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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bvLMM8/gwQcfxCOPPIL/+7//wzXXXIPbb78dq6urQ8f/9Kc/xSc/+Uncc889+OUvf4k777wTd955J44cOTLmyImAl156Cffddx9+9rOf4YUXXkCr1cJtt92GarW65f2cTidSqVTv39mzZ8cUMZHclVdeKXst/vjHPx45ludfUpNXX31V9tp94YUXAAC///u/P/I+PPcOx3JRm9x000248cYb8a//+q8AAFEUEY1G8Wd/9mf43Oc+NzD+E5/4BKrVKp577rnesZtvvhnXXnstHn/88bHFTTRMJpNBMBjESy+9hA9+8INDxxw+fBgPPPAACoXCeIMj6vP5z38ezz77LF5//fXzGs/zL6nZAw88gOeeew7Hjx+HRqMZ+DrPvaPxium6ZrOJ1157DbfeemvvmFarxa233opXXnll6H1eeeUV2XgAuP3220eOJxqnYrEIAPB6vVuOq1Qq2LdvH6LRKH73d38Xb7755jjCIxpw/PhxRCIRXHLJJbjrrrsQj8dHjuX5l9Sq2WziySefxN133z00Kd3Ac+9wTEzXZbNZdDodhEIh2fFQKISVlZWh91lZWdnReKJxEUURDzzwAN7//vfjqquuGjnu8ssvxze+8Q18+9vfxpNPPglRFPG+970PS0tLY4yWqDtjdfjwYTz//PN47LHHcPr0aXzgAx9AuVweOp7nX1KrZ599FoVCAX/0R380cgzPvaPplQ6AiHbffffdhyNHjmy5Rg8AbrnlFtxyyy292+973/tw6NAhfPWrX8UXv/jFvQ6TqOeOO+7o/f/d7343brrpJuzbtw/f/OY3cc899ygYGdHOfP3rX8cdd9yBSCQycgzPvaMxMV3n9/uh0+mQTqdlx9PpNObm5obeZ25ubkfjicbh/vvvx3PPPYeXX34ZCwsLO7qvwWDAddddhxMnTuxRdETnx+1247LLLhv5WuT5l9To7Nmz+P73v4///u//3tH9eO49h1P564xGI2644Qa8+OKLvWOiKOLFF1+UfarZ7JZbbpGNB4AXXnhh5HiivSRJEu6//35861vfwg9+8APs379/x4/R6XTwxhtvIBwO70GEROevUqng5MmTI1+LPP+SGj3xxBMIBoP46Ec/uqP78dy7iUQ9Tz/9tGQymaTDhw9LR48elf7kT/5Ecrvd0srKiiRJkvSHf/iH0uc+97ne+J/85CeSXq+X/umf/kl66623pEceeUQyGAzSG2+8odS3QDPsM5/5jORyuaQf/vCHUiqV6v2r1Wq9Mf2v4S984QvS9773PenkyZPSa6+9Jv3BH/yBZDabpTfffFOJb4Fm2F/+5V9KP/zhD6XTp09LP/nJT6Rbb71V8vv90urqqiRJPP+S+nU6HSkWi0l/8zd/M/A1nnvPH6fyN/nEJz6BTCaDhx9+GCsrK7j22mvx/PPP9xbYx+NxaLXnLjK/733vw1NPPYW/+7u/w9/+7d/i4MGDePbZZ7fcbEK0Vx577DEAwIc+9CHZ8SeeeKK3CL//NSwIAu69916srKzA4/HghhtuwE9/+lO8613vGlfYRACApaUlfPKTn0Qul0MgEMCv/dqv4Wc/+xkCgQAAnn9J/b7//e8jHo/j7rvvHvgaz73nj3VMiYiIiEgVuMaUiIiIiFSBiSkRERERqQITUyIiIiJSBSamRERERKQKTEyJiIiISBWYmBIRERGRKjAxJSIiIiJVYGJKRERERKrAxJSISAG5XA7BYBBnzpzZclw2m0UwGMTS0tJ4AiMiUhA7PxERKeDBBx9EuVzG1772tW3Hfvazn4UgCPj6178+hsiIiJTDxJSIaMxqtRrC4TC+973v4eabb952/JtvvokbbrgByWQSXq93DBESESmDU/lERGP23e9+FyaTqZeUCoKAu+66C4FAABaLBQcPHsQTTzzRG3/llVciEongW9/6llIhExGNhV7pAIiIZs2PfvQj3HDDDb3bf//3f4+jR4/if/7nf+D3+3HixAnU63XZfd773vfiRz/6Ee65555xh0tENDZMTImIxuzs2bOIRCK92/F4HNdddx3e8573AAAWFxcH7hOJRPDLX/5yXCESESmCU/lERGNWr9dhNpt7tz/zmc/g6aefxrXXXou//uu/xk9/+tOB+1gsFtRqtXGGSUQ0dkxMiYjGzO/3QxCE3u077rgDZ8+exV/8xV8gmUziIx/5CD772c/K7pPP5xEIBMYdKhHRWDExJSIas+uuuw5Hjx6VHQsEAvj0pz+NJ598El/+8pfxb//2b7KvHzlyBNddd904wyQiGjsmpkREY3b77bfjzTff7F01ffjhh/Htb38bJ06cwJtvvonnnnsOhw4d6o2v1Wp47bXXcNtttykVMhHRWDAxJSIas6uvvhrXX389vvnNbwIAjEYjHnroIbz73e/GBz/4Qeh0Ojz99NO98d/+9rcRi8XwgQ98QKmQiYjGggX2iYgU8J3vfAd/9Vd/hSNHjkCr3foawc0334w///M/x6c+9akxRUdEpAyWiyIiUsBHP/pRHD9+HMvLy4hGoyPHZbNZfOxjH8MnP/nJMUZHRKQMXjElIiIiIlXgGlMiIiIiUgUmpkRERESkCkxMiYiIiEgVmJgSERERkSowMSUiIiIiVWBiSkRERESqwMSUiIiIiFSBiSkRERERqQITUyIiIiJShf8Ps7vhZ9l8vZEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Absolute time marker specification\n", + "\n", + "# The blueprint has a list of tuples for each marker. The tuples are (switch_on_time, duration)\n", + "\n", + "# create a blueprint\n", + "bp_atm = bb.BluePrint()\n", + "bp_atm.setSR(100)\n", + "bp_atm.insertSegment(0, ramp, (0, 1), dur=3)\n", + "bp_atm.insertSegment(1, sine, (0.5, 1, 1, 0), dur=2)\n", + "bp_atm.insertSegment(2, ramp, (1, 0), dur=3)\n", + "\n", + "# specify markers in absolute time\n", + "bp_atm.marker1 = [(1, 0.5), (2, 0.5)]\n", + "bp_atm.marker2 = [(1.5, 0.2), (2.5, 0.1)]\n", + "\n", + "plotter(bp_atm)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:56.984095Z", + "iopub.status.busy": "2025-01-20T06:27:56.983745Z", + "iopub.status.idle": "2025-01-20T06:27:57.104335Z", + "shell.execute_reply": "2025-01-20T06:27:57.103793Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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D408Ip/P48iRI/j85z9v9VDIo37wgx/gsccew49//GN873vfQ7VaxYMPPoh8Pm/10MgjRkZG8Hd/93c4efIkXn31Vdx///34tV/7NZw+fdrqoVGLsMVWG9x9992488478bnPfQ4AoGkaRkdH8Xu/93v41Kc+ZfHoyGskScLzzz+PD3zgA1YPhTxscXERAwMD+MEPfoB3vvOdVg+HPKqnpwef+cxn8Oijj1o9FGoBzsS2WKVSwcmTJ/HAAw+sH5NlGQ888ABeeuklC0dGRGSddDoNYC1EEHWaqqp47rnnkM/nce+991o9HGoRn9UDcJulpSWoqorBwUHD8cHBQZw5c8aiURERWUfTNPzBH/wB7rvvPtxyyy1WD4c85LXXXsO9996LUqmEWCyG559/HjfddJPVw6IWYYglIqK2euyxx/D666/jhz/8odVDIY85fPgwTp06hXQ6ja9//et4+OGH8YMf/IBB1iUYYlusr68PiqJgfn7ecHx+fh67du2yaFRERNb4xCc+gW9961s4ceIERkZGrB4OeUwgEMCBAwcAAMeOHcMrr7yCf/zHf8QXv/hFi0dGrcCa2BYLBAI4duwYXnjhhfVjmqbhhRdeYB0OEXmGruv4xCc+geeffx7//d//jfHxcauHRARN01Aul60eBrUIZ2Lb4Pjx43j44Ydxxx134K677sJTTz2FfD6PRx55xOqhkUfkcjmcP39+/fHExAROnTqFnp4e7Nmzx8KRkVc89thjePbZZ/HNb34T8Xgcc3NzAIBkMolwOGzx6MgLHn/8cTz00EPYs2cPstksnn32WXz/+9/Hd7/7XauHRi3CFltt8rnPfQ6f+cxnMDc3h9tuuw3/9E//hLvvvtvqYZFHfP/738cv//Ivm44//PDDeOaZZzo/IPIcSZIaHn/66afx0Y9+tLODIU969NFH8cILL+DKlStIJpO49dZb8ad/+qd4z3veY/XQqEUYYomIiIjIcVgTS0RERESOwxBLRERERI7DEEtEREREjsMQS0RERESOwxBLRERERI7DEEtEREREjsMQS0RERESOwxBLRERERI7DEEtE5ADLy8sYGBjA5OTkNZ+3tLSEgYEBXL58uTMDIyKyCHfsIiJygOPHjyObzeJLX/rSdZ/7yU9+EqlUCl/+8pc7MDIiImswxBIR2VyhUMDu3bvx3e9+F/fcc891n3/69GkcO3YMs7Oz6Onp6cAIiYg6j+UEREQ291//9V8IBoPrATaVSuEjH/kI+vv7EQ6HcfDgQTz99NPrz7/55psxNDSE559/3qohExG1nc/qARAR0bW9+OKLOHbs2PrjP//zP8cbb7yBb3/72+jr68P58+dRLBYNX3PXXXfhxRdfxKOPPtrp4RIRdQRDLBGRzV26dAlDQ0Prj6empnD06FHccccdAICxsTHT1wwNDeFnP/tZp4ZIRNRxLCcgIrK5YrGIUCi0/vh3f/d38dxzz+G2227Dn/zJn+BHP/qR6WvC4TAKhUInh0lE1FEMsURENtfX14dUKrX++KGHHsKlS5fwh3/4h5idncW73/1ufPKTnzR8zcrKCvr7+zs9VCKijmGIJSKyuaNHj+KNN94wHOvv78fDDz+Mr371q3jqqafwL//yL4a/f/3113H06NFODpOIqKMYYomIbO69730vTp8+vT4b+8QTT+Cb3/wmzp8/j9OnT+Nb3/oWbrzxxvXnFwoFnDx5Eg8++KBVQyYiajuGWCIim3vb296G22+/Hf/+7/8OAAgEAnj88cdx66234p3vfCcURcFzzz23/vxvfvOb2LNnD97xjndYNWQiorbjZgdERA7wn//5n/jjP/5jvP7665Dla88/3HPPPfj93/99fPjDH+7Q6IiIOo8ttoiIHOB973sfzp07h5mZGYyOjjZ93tLSEj74wQ/it37rtzo4OiKizuNMLBERERE5DmtiiYiIiMhxGGKJiIiIyHEYYomIiIjIcRhiiYiIiMhxGGKJiIiIyHEYYomIiIjIcRhiiYiIiMhxGGKJiIiIyHEYYomIiIjIcf5/Hc8DC6lFJpIAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArEAAAEaCAYAAAAG6Q+dAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAO85JREFUeJzt3XuMHeV9N/DvzJw5t735sus1a69tDA4Xg6/YJs3bFAUKoigSTamihD+4hESNoGniphVEKklUKFWrRlRJWtKqQKuIQlS9KVJoqSJSBfI2FLBxwDYGQ3xZX9be9a5399znzMz7x3KWneeZ3T3n7JmZZ85+PxJKzpzbM3t2vd/9zfP8Hs11XRdERERERDGiRz0AIiIiIqJGMcQSERERUewwxBIRERFR7DDEEhEREVHsMMQSERERUewwxBIRERFR7DDEEhEREVHsMMQSERERUewkoh5AmBzHwZkzZ9DV1QVN06IeDhEREREJXNfF1NQUBgYGoOtz11uXVIg9c+YMBgcHox4GERERES1gaGgIa9eunfP+JRViu7q6AEx/Ubq7uyMeDUXBtm0cePVXAIBt12+FYRgRj4hq+NmoiZ8LhYHfZ+qK4rOZnJzE4ODgTG6by5IKsbUpBN3d3QyxS5Rt2+js6AQw/X3AfyjVwc9GTfxcKAz8PlNXlJ/NQlM/ubCLiIiIiGKHIZaIiIiIYochloiIiIhihyGWiIiIiGJnSS3sCtv40aNwLCvqYdAsjuNgcmgIADD2Tnre/nMULn424TvyzDOw8vl5H+O4Lo6PlQAAU/83Db2OHttmRweu/PznWzJGWhr486+u2mejGwaA7VEPx4MhNkCOZcGpVqMeBs3iOA5c257+/9UqwH8olcHPJnyVqam6Qmy1WP3w8VZdIdZ1HP7bRw3hz7+6ap+NE/VAfDDEBkg3zaiHQCLHgfZhexA9keBf+yrhZxO6ZFcXtAW+zo7rImGVPnx8/ZVYPcFfL9QA/vyr68PPRlew7Rn/lQnQ8k2boh4CCWzbRveHl0ZXXHUVexEqhJ9N+D7xyCMLPsa2bXT9vzcBADs+sZ2fCwWCP//qmv3ZqIZ/6hARERFR7DDEEhEREVHsMMQSERERUewwxBIRERFR7DDEEhEREVHssDtBgI4eHYdlqdhZbelyHAcnh6YAAJl3xtjGRSH8bNTEz6U5zzxzBPl8fZvddHSY+Pznrwx4RGrj95m6ap9NwtCwI+rBCBhiA2RZDqpVhliVOI4Dx57+TKpVh/20FcLPRk38XJozNVWpO8Q6jrvkf1fw+0xdtc+mquDFe4bYAJmmeh/4Uuc4gG5Mfy6JhM6/9hXCz0ZN/Fya09WVhK4vvDEEMF2JTSSW9teV32fqqn02CaO+7+cwMcQGaNOm5VEPgQS2baM41gUAuOqqFWyorRB+Nmri59KcRx75RNRDiBV+n6lr9mejGv6pQ0RERESxwxBLRERERLHDEEtEREREscMQS0RERESxwxBLRERERLHDEEtEREREscMQS0RERESxwxBLRERERLHDEEtEREREscMQS0RERESxwxBLRERERLETqxD78ssv49Of/jQGBgagaRr+/d//PeohEREREVEEElEPoBH5fB5bt27Fvffei8985jNRD4eIWsh1gXI+B9u2MDV6DroRq7+x25ZjOyhMjkPTdNjVKgzDiHpIobCrFiqFPAwziWQmG/VwQmFbFirFpXXOFG+xCrG33norbr311qiHQUQtZlsWhg7tx/nj7wEATnda0HWGWBU4joMLQ6cAAO+/VsWqDZvQO7gx4lEFa3To1xg9+QFc2wYALB9Yh1Ubr4Cut2+AnxwZxtn3DsKxqwCATM9yDF69A4ZpRjwyorm19W+JcrmMyclJz39EpJZKsYDjB15F4eKFqIdCC3BtGyPH3sOFU8eiHkpgRod+jZFj780EWAAYP3MSZ989GOGogjU5MozT7xyYCbAAUJwYx8mDb8CZ9XUgUk1bh9jHHnsMPT09M/8NDg5GPSQimqUwMY7jB15FpZiPeijUgJHjR1HO56IeRsuV8zmMHD/qe9/kyFnkxkZCHlHwHLuKcx+843tfaWoCY6ePhzsgogbEajpBox566CHs3bt35vbk5CSDLJEiJs6fwdn3DsJ1HM9xTTeQzHZyOoEiHMeBkTBhV62ZY67j4MKpYxi44toIR9Z6o0MfTE/OnsP5Y++ic0VfiCMK3tjpE6hWynPef+HUcSwfWAcjwWkFpJ62DrGpVAqpVCrqYRCRYOTE+xg98b503Exn0LvuMmzcuWfJLCBSnW3buFjK4uLwKc/xyZGzWHXpx5BItse/sVapiMmRYc8xw0zCtiozt8v5HAoT48j2LA97eIFwXRfjZ056jukJE86sP1icqoWLw6ewcu2lYQ+PaEEsdRBRaBzHxpl33/INsJ0r+qZDkZmMYGS0kK7e1dBmVcddx8HEuTMRjqi1JkbOeqqwupHAxuv+D5LZTs/jLp47HfbQApO/eEGqwq7fsgtdvf2eY5Pnz4Y5LKK6xSrE5nI5HDhwAAcOHAAAHDt2DAcOHMDJkyfnfyIRRa5qVXDy7Td8g8/yNeux5uodbb36O+6MRALdfZd4jk1dOBfRaFpvSqjCdvetRsJMYln/Gs/xyZGzcJz2WOw0IQTydGc30p3dWHaJd9pdKTfJeeukpFiF2DfeeAPbt2/H9u3bAQB79+7F9u3b8fDDD0c8MiKaT6WYx/EDr6I4Me69Q9PQf/nVWH3ZVdC0aMZG9RNDbHHy4rzzKeOiUiyglPN2r6mda0//gOe4a9soiN/HMeQ6DvLjo55jPR8G9o6eFTCEaSLiVAsiFcRqTuwNN9wAd55J90SknsLEGE4dfhO2ZXmO60YCa67a2nYLZdpZpmc5dCPhacU0deE8ll8S7wWzYpgzzOTMvNdEMoVM9zIUJy9+9PixUXQu7w1ziC1XzE1IP5O1aQSarqO7t98zXzZ/cQy96y4LdYxEC4lVJZaI4uXiudM4+fYb0i/LRCqN9Vt3M8DGjK7r6FjhDW/5NujvK55D54pez/xf8fs0J4TeOMqPe8851dEJM5Weud0hhPTi5Dh7xpJyGGKJKBAjx4/i7LtvSy200p3duHT7x5Hu7I5oZLQYYgUy7pfWXddFYWLMcyy7bKXnthjoKoUcrHIp8LEFSaw+i+eY7VmO2XN8XGd6+2EilTDEElFLOY6N0+/8CqMnP5Du61y5Cuu37mmbtkxLUbZnhee2XSnHetFPOT8lXSnoEM4x3dkNXeiTOnt6Qdw4ji3NAe4QgruRMJHp6vEcK1z0hn2iqDHEElHLVK0KTr71OiZH5JY8K9ZuwNqrt0Nn/9dYS2ay0h8hca7GFoQwamayMNMZzzFN05DpXiY8L77nXMpNSldIxPMD5Ip0nIM7tSeGWCJqiXIhN92BQPxFp2lYffnV6N94JTS2IGgLYjU2ziG2NHXRczvb7b+RQVYIeXEOdOLYUx1dvjtySeecm5DCL1GUGGKJaNHyFy/g+IH/hVUseI7rRgKD1+zE8oF1EY2MgiBW7cRL03FSnJzw3BYvoc8c9znnuC50EkOsXxUWANLC18K1bZQLuYBGRdQ4hlgiWpSLw6cwdHCfZ6tKYHoL2fXb9sS+FRHJ0l3eRXnlQi6Wgc62LGk+rxjcajJdyzwLneC6KOenAhxdcIpTQnCfI8QmzCTMTHbe5xJFiSGWiJriui7OH3sPZ987KHcg6OrBhm3XI93RFdHoKEjpjm4p0MWxGlvMeQOZputzfs/qhoFUtsNzLI7nXLUqqAqdFTKd/sEdkCvTDLGkEoZYImqYY9s4feRXuDD0a+m+rt5+rN+ymx0I2th0oOv0HCvl4hduxBCa7uz29IcViW3h4hhixTFrhoGkEM5nE8+5HMNzpvbFEEtEDalWyjj59uvSXvMAsHLwUqy5ahs7ECwB4mX3OAY6cTpAaoErB+0YYtMdXfMuuJRCbCHHxV2kjFhtO0tE0Srncxg6tA9Wqeg5ruk6Vl9+NZatXhvRyChs6c5uzK69lvLxW/AjhtiFpr+IIbcW6Oar3qqmnBPOeYFNR8Rzdh0HlWIBqY7OOZ5BFJ74/OQRUaRy46M4/qv/lQKsnjAxuHknA+wSkxZCTCVmFTrHsVEueBd1pTobq8S6joNyzDZ6KDVYfU6YSSRmbUc7/Rrxq0BTe2KIJaIFjZ8dmrMDwYate9CxfOUcz6R2lcr6V+jiolLIA67rObZQJdZImNJGCHHqUOA4ttyNoY7Fl+Jj4nTO1N4YYoloTq7r4tyvj2D46CHpF36mexk2bLuelxWXKMM0pcV75UJ8wo1YkTQzWejGwjPsksKCNrGaqzK/4F7Pz29KnBcbw6kj1J4YYonIl2PbOP3OAYydOi7d19W3Guuu3cUOBEuceCk6TvNiK0LT/nrbwYlttsTXUZm4UYGZztQV3FMZ7znHbQoFtS8u7CIiSbVSxtCh/Sj59IRcue4y9K2/nFvIElIdnciPj87cjlWgEwJ3MjN3m6nZ5OAen+qzWDUWq8pzEVtwWaUiHMeGrrMLCUWLIZaIPEr5KZw6tJ8dCGhBYq9Ycb6lysRqongucxEfZxULcGw7Fm3lxD8yxKryXJLCrl1wXVjFIqcSUeQ4nYCIZuTGRnDiwBwdCK5hBwLyEquXlWIBrjDnUkWObcMSFqHVG8j8wm5cwrtYfV6oM0GNkfCZ/1yMT9Wd2hdDLBEBAMbPnMTQof1w7KrnuJnJYsO2PehYxg4E5CVW6FzHgVUuzvFodfiFznqnE+iGIbWcikNXBtdxUCkJwb3OcwbkqQeVGC1oo/bFEEu0xLmui3MfHMHw+4flDgQ9y6c7ENR5qZWWlkQyBT1heo7FIdD5L3CqfzqAXIFWP9BZ5aL0811vcAd8FrTF4HOm9scQS7SEOXYVpw6/ibHTx6X7ulddgnXXXoeEmQx/YBQb8mr9GAQ6YbpMI2Fu+vHeCnQcAp04RsM0YZjmHI+WxTG4U/vjwi6iJcoql3Dq0H7f/d97112Gvg2bIhgVxU0yk0Vx8uLM7TiEG3GM0sKlBUghthS/ENtwcBf+WIlTf1xqXwyxREtQKTeJoUP7US2XPMc1Xcclm65BT/9ARCOjuPFb3KU6OdA1GmLjeM6LC+7i/FmnaqFaKbNXNEWKIZZoicmNjeDUOwfg2rbnuGGaWHv1dmR7VkQ0Mooj+dK6+hW6RVclhXO2K2XYVQtGov7L82Fb7DknUmlohuH5d6NcyDHEUqQ4J5ZoCRk7fQJDh/ZLATaZ6cCGbdczwFLDxDBklYpwhO8vldhVC7ZV8Rwz05mGXsNMZwBhsw9xnq1qxCkPjZ6zpmlyBZpTCihiDLFES4DrOBh+/zDOffCOtEI527MC67ftabgyQwT4X5ZWeY6oX9hsNNDpugFTaLMldjxQies4i17MBnD7WVIPQyxRm6t1IBg/c1K6r6d/AIPX7mQHAmqabiR8+qaqG27Ey+pmOtPU9ql+FWhVVUoFn/Zajc2J9XuOuGEEUdg4J5aojVmlIoYO7UfZZ3/3vg2b0LvusghGRe0mmenwLBJUeaGTWCVuJszVnpcfn/W6Cgd3MWAbZrKp+bum1JVB3eBOSwNDLFGbKuUmMXRwH6qVsue4puu45GPXoGcVOxBQayQzWRQuXpi5rfJcSbkS22yIjU+HgsV2Jph5nvC1shSeNkJLA0MsURuaGj2H0+++5dOBIPlhB4LlEY2M2lGc+qaKl8AXU4mdTeVK7GI7E9SIc4ddx2GbLYoUQyxRm7lw6hjO//pd6Xgy24nBzTua/qVNNBe5QqfuZWapKtmiSqxtWahaFSXnl7eqEptIpqDpOlzHmfXaBYZYigwXdhG1CddxcPboId8Am122Ehu27mGApUCIFbpquQTHUa/NlmPb0vQacZ5nvcxUGpru/RWq6kKnxW7uUKNpmjT9QuU/WKj9McQStQG7amHo0H5cPDsk3dezei3WXbOzoX3SiRrh16KqWi77PDJaftMcmq3EaroudWWwhB3wVOA6jjSuZucBTz/X+1lbZYZYig5DLFHMWaUiTvzqf5EfH5Xu67v0Yxj42DVSxYiolYyECV1Y7a7ivFixUppIpqAbjbfXqjFTQqBTsCpplUtye61FhNikEGJVXtBG7Y9zYolirDh5EUOH34Tt04Fg4Iot6O5bHdHIaKlJpjMo5ayZ2yoGOmnXqkVOr4lDVVIck24kFnVVRjxnFf9YoaWDIZYopiZHh3HmyFueRRbAdAeCwc07kOleFs3AaEky0xmUcpMzt1UMsdKuVYuoSE4/Xwx06p9zo7uTieK0iI/aH0MsUQxdGDqG88fm6EBwzY5F/3ImapRUlVQw3LQ60MViOkGrz3mORXzN7HpGtFgMsUQx4joOht8/jIvDp6T7OpavxJqrtjW1Ew/RYkmr1pW8tC4scBIWZjVKDu7qXVoXP4dWh1hgOiinsp2Lel2iZnC1B1FMTHcg2OcbYJetXovBzTsZYCkyS/HS+lzN/1Uifg6LDe5GwoQh9MJVsQJNSwMrsUQxUCkVMHRwPyqFnHTfqkuvwMrBSyMYFdFHxEvrdqUMx7YXtfq/lWzLgmNXPccWG2L9mv9bpaJSzf+rQvW5FVONzHQGtlWZuc0QS1FhJZZIcYXJcRx/81UpwGqGgTVXb2OAJSXMdZlZFdL0Bk2DmVxcVVLTNJ9eseqcs3+P2MUFd0AOwuxQQFFhiCVS2OTIWZx863VP1QMAjGQK67fsRncvW2iRGnTDgCFUIFUKdNJUAp8dt5qh8jQKvx6xYsW8GXFYxEdLA6cTEClq9OQHGDl+VDqe6ujC4OYdLamoELVSMp1BcdacUJUa4YuBWqygNkvlDgVSj9iE2ZKd++T+uOrtVEZLA0MskWJcx8HZo4cwce60dF/H8l6suWorF3CRksxUBkVcnLmtUiVWrJCKFdRmqVyV9Ks+t4L4OgyxFBWGWCKF2JaFU+8cQOHiBem+5QPr0L/xSm4hS8pSOdCJC5xacVkdUPucW92NYeZ1FF/ER0sHQyyRIirFAoYO7kOlmJfu67/sSqxYsyH8QRE1IJlRdzcnqdVUUJXYchGu60LTtJa8/mK0ukfsR68jV3SrlRKSmY6WvD5RvRhiiRRQmBjHqcNvSgu4NMPAmiu2oKu3P6KREdVPbC2l0mVm+dJ6awKduFLfdRzYVkWJNltBTaHQjQT0hAmnas0cs0oMsRQ+hliiiE2cP4Oz7x309JoEpgPB4DU7ke7sjmhkRI2RLjNbFSUuM9tVyxO4AP9qYjMMMyn1iq2UCkqE2KCmUEy/Vhrl2SFWofnPtHRwch1RhEZOvI8zR96SAmyqsxsbtn+cAZZiZa7LzFHzqwi3qjuBX69YMTxGIagesTOvxcVdpACGWKIIOI6NM+++hdET70v3da7ow4atu1u2kpgoLLXLzLNZpejDjTiVIJFMQddbVx2W22xFf87VSlnqEduq4A6wzRapgdMJiEJWtSo4dfhNFCfGpfuWr1mP/kuvYAcCii0VLzMHtUp/5vXEqqSC1WfNMJAwky17fbn6HP3nTEsPQyxRiCrFPE4e3AdLbAKvaejfeCVWrFkfzcCIWsRMpVHOT83crs7a/CAq0ir9Fs4NnX499aYTSOe8yC12RfJ0gug/Z1p6GGKJQlKYGPuwA4F3gYlmGFh71TZ0ruiLaGREraNi39SqELBatajro9dT75zl+bCtDrFyazGisDHEEoXg4rnTGD56SO5AkEpjcPMOLuCitiFeZlZhrqS05WyLq5IqthYTq8GtPmexEuvaNqpWpaVTFogWwhBLFLCR40cxevID6Xi6sxtrN+/gAi5qKyquWhenNLT6Z07F1mJSJbbF55xIpgBN8yweq5ZLDLEUKq4eIQqI49g4/c6vfANs58pVWM8OBNSG5BAb7WVm13UDbTUFAImU3BM26tZiUo/YFp+zpuvyZ63ANApaWhhiiQJQtSo4+dbrmBw5K923Ys0GrL16O3SDF0Ko/YjTCVzbluaBh8m31VSLNyIwEqbcWiziCrT4/kFsvqDi1BFaWvhblKjFyoUchg7uk6sSmobVl12F5QProhkYUQj8VsFb5SIM0/R5dPDESrCm64EEOrm1WHSBzrFtaQvrVndkmH7NNGZ/daOuutPSw0osUQvlL17A8QP/KwVY3UhgcPMOBlhqe34hMcpAJ3YmaGXD//leN8o2W35TGfymPCyW3KGAlVgKFyuxRC1ycfgUht8/LHUgMNMZrN28A+mOrohGRhQuM53xLKaKMtwE3SP2o9dVZ36o+PXWEyaMROsr4Sr2x6WlhSGWaJFc18XI8aO4MPRr6b50Vw8GN+8I5PIlkapUqkrKq/SD+VmUd+2Krvl/aOesYH9car2qZaGUn0LCVO/32KJCrGVZGB4eRqFQQF9fH1asWNGqcRHFgmPbOPPe25gaGZbu6+rtx8AVWyJts0MUBZU6FATdL3XmdRXahlU654Cqz+If59VKGa7jcNvsmLKrFsqFHMr5nOd/rVIRI8dPYdklg1EPUdJwiJ2amsIPf/hDPPvss3jttddQqVTgui40TcPatWtx880340tf+hJ27doVxHiJlFGtlHHq8JsoTl6U7ls5eCn6NnwMmqaFPzCiiKk0VzLoHrFzva5VUqn6HNQ5y+HYqpSQTGcDeT9qDce2p0NqIYdyfgrlQh7l/NSCV0xUrLQ3FGK/853v4NFHH8Vll12GT3/60/jGN76BgYEBZDIZjI2N4eDBg3jllVdw8803Y8+ePfjud7+LTZs2BTV2osiU8zkMHfLvQHDJps1YtnptNAMjUoBKcyXFn9FW90udeV0h0Dl2FXbVCmQu6kLCCrGGaUI3EnDs6syxapkhVhWu46BczM8E1Uohh1J+Clax0NTrqbhwr6EQ+/rrr+Pll1/G5s2bfe/fvXs37r33XjzxxBN46qmn8MorrzDEUtvJjY/i9Du/glP19r7UEybWXLUVnct7IxoZkRr8+ofWrtiFyXUcqRIb1Px0v9X/VrkUSYiVpxMEt6mKmU6jnM/N3LZKJaAnsLcjH67joFIqoFzIoVLIo5SfQjmfg1UqSAuNm6VpmpJXFhsKsf/6r/9a1+NSqRT+4A/+oKkBEals/OwQht8/LDVPN9MZDG7eiVRHZ0QjI1KHVPlzXVQr5dB3qLN8Wk0F1Z1A1w0YyRTsWaG5Wi4BEXQlCasSC0zPt/WEWPaKDcz07nPFmaA6XVnNoVLItS6s6jqSmSxS2S6kOjqRynbCTGeR147EP8QCwB133IH77rsPt9xyi5InRBQE13Vx/ti7GDt1XLov070Ma6/ezg4ERB9KJFPQdN3zi9UqF0MPsWKPWM0wAt10wUylPSE2isuvjl2VrhIF+XWXF/Gpd8k5jqqVshRUy/mcZ+rGYpmZLNIdXUhlO5Hq6EQy24lUpkNamGfbtrJ5r+EQOz4+jttuuw0DAwO45557cPfdd2Pjxo1BjI1ICY5t48y7b2Fq9Jx0X1ffagx87Fp2ICASJFJpz9y7KObFhtUj9qPXT6M0NTHr/SM4Z58FZUF1ZADU6o8bR1WrMhNQyx/OWa0Uci3dqtlMZ5DMdiL9YVBNd3Qhmeloi99bDYfYl156CSdOnMBTTz2Ff/mXf8Gjjz6K3/qt38J9992H3/u930MqoH50RFGoVsoYOrTf84upZuXgRvRt2KTsX6hEUTJTGU+IjWK1flj9UmtU6I8rTqEwzGSgYUU6Z58pHDRdIfe2rpqetyrO2V4MI5nyBtVsB1LZzkjmZYelqT6x69evx7e+9S1861vfws9+9jM8+eST+OIXv4gHHngAn/vc53Dvvfdi586drR4rUahK+SmcOrRfqixouo7Vl1/NDgRE85Cb/4cfbsLql1qjwqV18ZyDnsKhUmsxFTi2jUox7wmqtV6rraInTKQ6akG1E6lsB1IdXUiYyZa9R1wseseuT33qU/jUpz6FqakpPPPMM/jGN76BH/zgB6hWWzdvgyhsubGR6Q4EwvwjPWFi7dXb0LFsZUQjI4oHFdpsSZXYgOety/1xw7+0LoalIDsTAGq1FguT6zioFAuzeq1OV1grpYK08LdZmmFIQTXd0cX1F7O0ZNvZY8eO4emnn8bTTz+NiYkJ3HTTTa14WaJIjJ85ieEP3pE7EGSyGNy8A6ksOxAQLUSFLUmlqmRAPWJnXt8nuIfdWkzc7jboSqxKrcWC4LourFLRE1TLhRwqxXxrOwLMCqq1hVZmKsPpagtoOsSWSiX827/9G5588km8/PLLGBwcxBe+8AXcc889GBxUb2syooW4rovzv34XY6ePS/dlupdh7eYdS/JyDVEzxGpRJIucpC1nw50T6zoObKsSauVM3O426EqsrhswzCRsqzJrDNG0Flssq1QUtl2dQrmYh2vbrXkDTUMy0zHTuqr2v8l0llv1NqnhEPvaa6/hySefxHPPPYdSqYTf/d3fxYsvvogbb7yRfzFQbDl2FaePvIXchfPSfd19l+CSK66Brsd/JSdRWMTLzLZVgePYof0cObbtCVZ+Y2o1/9ZipVBDrFUOtxJbe4/ZX2vV22xVK2VvUC3kUC7kpdZki2FmstNBtRZWO7qQzGT5e6TFGg6x119/PbZu3Yo///M/x5133only5cHMS6i0FjlEk4d2o9SblK6r3fdZejbwF3niBrld5m5Wi4jmQlnS1K/Vd9+Y2olTdOQSKY8Uyeq5RLQFd4WVmG3FQOmp2nM/vczym2GZ7MtSwqq5fyU9MfNYiRSaU9VdXo6QAd0oyWzNWkBDX2VT548iTfeeAM7duyo6/GnT5/GmjVrmhoYURhKuUkMHdov/aOr6TpWb9qMZf38/iVqhpEwoSdMT3XLKhdDC7FimNMTZijzNBOptCfEhlmVrFoV6dJ30MEdiH7qiKd9VW06QH6qte2rzORMQJ2Zt5rtDHTzDFpYQyF2165duP3223Hfffdh165dvo+ZmJjAj370I/zt3/4tvvSlL+ErX/lKSwZa8/3vfx9//dd/jeHhYWzduhXf/e53sXv37pa+By0N+bFRnHnvLekffcM0sfbq7cj2rIhoZETtwUylUPaE2PDCTdg9YmfeJ5nG7PgcZocCvwqoGeBGBzPvIXVlCOZzdhwblUJemrfa8vZVH/ZXTXV8tPUqOwKoqaEQe/jwYfzFX/wFfvu3fxvpdBo7d+7EwMAA0uk0xsfHcfjwYRw6dAg7duzAX/3VX+F3fud3WjrY5557Dnv37sUTTzyBPXv24PHHH8ctt9yCd999F6tWrWrpe1F7m7pwHkOHR6AL87jNTBbrrtmJZKYjopERtY9EMo1yPjdzO8zLzGH3iK0x02KHgtZVAxfit5AtjAVDYrV3sZ+z67gol4UFVoU8KsV8S9tXpTLebgCpbGfgHSyotRoKsStXrsTf/M3f4JFHHsELL7yAX/ziFzhx4gSKxSJ6e3tx55134pZbbsE111wTyGC/853v4Itf/CLuueceAMATTzyBF154AU8++SQefPDBQN6zGdVKGWfeOxj1MMiH4zg4d+woKoUc+pevBWaF2EzPcqy9ejs7EBC1SJTN/8PuEVsjbvEaZSU2rEDWiv64VcvC+PAQyrkc3sMoWrVMXNP16Y4AwrxVM832Ve2gqZnHmUwGd9xxB+64445Wj2dOlUoF+/btw0MPPTRzTNd13HTTTfjlL3/p+5xyuYzyrL+CJyflhTtBcBwb+bGRUN6LGuM4DiqFnHS8p38Aqzdt5spRohYSQ1SUldjwAp24DWu0ldgwiOfsOg6qVqWhgsDwe28jd2Fk5vkNV5A1Dcl0Vlhg1Ylkhu2r2llsls+Njo7Ctm309/d7jvf39+PIkSO+z3nsscfw7W9/O4zhUYz1bdiE3nWXRT0MorYT5YKfyAKduMlDiBseRNGZAPD/2lbLpYZCbH5irO7HmunMrMrq9LzVZKYDusEixFITmxDbjIceegh79+6duT05OcmNGGiGpusYuHILelYNRD0UorYkhqhqJcqFXREFOtdFtVIOpV+rOP82jM4EwPS/pYlkylN1tsolpDu7638Rn7muiWTKE1RT2U4ksx1tsxsYLV5sQmxvby8Mw8C5c+c8x8+dO4fVq1f7PieVSiEV0g/xbEbCxKqNV4T+vrQwx3YwPOZC03Vs3PEJpDvjt6sMUVyIIcq2LDi2HXjFzLGrUuP6sAKd/4YHxVBCbFSVWGC6OiqG2EaIW7iu37YH2W72oaf5xSbEJpNJ7Ny5Ey+99BJuv/12ANPzG1966SU88MAD0Q5OYCRMrFx7adTDIB+2baP7xDiA6U4ERBQcvxBllYtIZTsDfV+/ABVmoEuk0rCKhZnbYXQocD+s+M4WRnCuEbe3Xez8Z8NgtZUWFpsQCwB79+7FXXfdheuuuw67d+/G448/jnw+P9OtgIiI1KEbBgzThG15e8WGHWIN0wx1vqSZynhCbBgdCmyrIlUzxWAZJLEfbSPnLI4bAFrWnoDaWqxC7Gc/+1mMjIzg4YcfxvDwMLZt24YXX3xRWuxFRERqSCTTnhAbRoeCqHrE1ogbK4SxoE18j9o81bCI/XEbOWcX8nxYTWNHAVpYrEIsADzwwAPKTR8gIiJ/ZiqNcn5q5nYYLaei6hFbI4bmMIK7WPlMJFOh9kEV++M2dM5++xewEkt14J86REQUGKnlVAu3CJ1LVD1iZ94vgtZicvU5vKkEgH9/XLfu3bV8KrFMsVQHhlgiIgpMFL1ipR6xYQe6CDZ5kFuKRXvOruPAtip1Pdc37HI3LaoDQywREQVGWrUeQq/YqAOdfM5l/8VLLRRVX9waw0xKO2PVW3X3C7HcEpbqwRBLRESBEQOkVQp/fmjoVcmk/H5WwOE96ukEmqbJVfd6z5mVWGoSQywREQVGrAg6dhW2sBFBK9mWBde2PcfCDnSGaUITWnoFPaUg6uoz0NpesZwTS/VgiCUiosD47ZQVZPN/v/6kfpXRoInhPci5wK7jyIvZIgixUtW9znNmn1hqFkMsEREFRtcNGGbScyzI5v/Soq4Pt4ENW7OBrhl+bcvCrj4Diwixvn1imWJpYQyxREQUKL/2S0GJur1WTau3YZ2PtNGBYSAh/OEQhqb74/p24mKIpYUxxBIRUaDEQBdkr9io22vVhNkrVlrIFsH0ien3bfacWYml5jDEEhFRoMK8tK5MoAuxV6w4x1jcAjYs0jnX2VqMfWKpWQyxREQUqDB7xUqr9CMKdFL1OcTgLm4BGxap6u26dU0dYZ9YahZDLBERBUquxIY3Jza66QTe97WtChyh9VerqNBeCwASfhse1BPexRDLAEt1YoglIqJAye2mgpkT6/pU/sLeuWrmfX0qwEFVoFVZzAa0purOKizViyGWiIgCJfaKdW0bttX6DQ9sqyLNwYyqKqkbCegJ03MsqCkFqixmA3z+YKljhzbfObFEdWCIJSKiQPluwxpANVbseqDputSjNkxhLGhzbBu2VfG+b0RzYgGfc66nEsvpBNQkhlgiIgqUputICO2XgugV61eRjPLSdBi9Yv0u10e1mA1o7pzFSqymMZpQffidQkREgQujV6wq7bVm3j+ESqx4uV5PmNCNRMvfp17SOdf1OYshtoUDorbGEEtERIELJdAp0l5r5v3DOGehEhvVHOC53t9qqsUWUyzVhyGWiIgCF0avWLm9VnSr9KffP4TpBIq016oRz9mulOE4C7QWkzIsQyzVhyGWiIgCF0avWKkSK8zDDVs40wmEjQ4Uq8QC8o5iIlecTtDSEVE7Y4glIqLAyYEuiDmx6vRLBeRzdqoWHLva0vdQZaODGsNnTu6Cn7W4sEtnNKH68DuFiIgC53dpvZX9QR3Hhi3Mv4y6Kum3/Ws9fVMbIU7LiGpzh9nkz3qBSiz7xFKTGGKJiChwyXTWc9t1HKm/6WL4BaWoq5K6YUh9auvqm9oAMRSLG0tEoeGqO/vEUpMYYomIKHCGmZQuE1dKhZa9vhiUdCMBQ9gxKwpioGvl4i7bZ3qCCpVY6ZwX6FAgz4lliKX6MMQSEVHgNE0LdLW+au21asR5ua3sj+u3UEyFSmzDPYHZnYCaxBBLREShSAqBrtLCQKdae60aKdC1cEGbdM7JFHTdaNnrN6vRrgyu63huR7nLGsULQywREYVCDJaBVmIjbq9VE2RwF18r6m4MNeKUhgVbi7ESS01iiCUiolAEGehUa69VIwW6Vp6zMKdYhfmwgDyVw6lasKvWnI9nn1hqFkMsERGFouG5kg0QX0udQCdUnytluI4zx6MbI52zIsHdr7XZvJ81uxNQkxhiiYgoFGIltpXzQ6WqpCKBThqH67Zs5y5VQ6yuG4v6g4VzYqleDLFERBQKsTrq2jaqLegV61fdVCXQGQkTutDqq1XhXdUpFIBPV4Z5zlmqTDPEUp0YYomIKBSJZEoKKK2YUiD2m9V0ffq9FCGt1m/BOTt2VdosIurNHWZrZP6ztK6Ls2KpTgyxREQUCk3XG9/NqQ5+82FVuiQdRK9Yv1CoyjxgoMEFbZwTS01iiCUiotAEsVpf1bmhNY1cWq+Xb49YI/oesTWNBXehOwEzLNWJIZaIiEIjtl9qxSIn1UOstKCttPhzVrVHbI0c3Oc+Z1esxHI6AdWJIZaIiEITSCW2rHigk5r/t6L6rGaP2BpxPE7Vgm3594oVQ6xKU0FIbQyxREQUmkYqdPUSq5Ji5TNqfue82F6xqlefzVRaXsQ3V3jnnFhqEkMsERGFptWVWNd1pfmhqlcl4bqwKosL76qHWL9FfGIXiTmfyxBLdWKIJSKi0Ihha77LzPWo+lQ1VQt0hmlCNxKeY4sN7yr3iK2p9w8WeU4sUX0YYomIKDR+l5krxXzTrydeotYMQ6kesTWtbLOleo/YmrrPmdMJqEkMsUREFBpN16VwU+9lZj+qz4etaWWIVb1HbE2958yFXdQshlgiIgpVMp313F5MoPPb6EBF4jlXis0Hd0t4rmo9Ymvq74/LEEvNYYglIqJQJTMtDHSKL3Cqkc55UdVnob2W8NqqqHfrWfaJpWYxxBIRUahaO51ACHSKhlgxaC4muIvPTWY6mn6tIJlC9dm1bf+WalKGZYil+jDEEhFRqMTQJV4eb4QU6NKKViWFEOtULVSFxVn1kkOsmuecSKagCdMc/BbxueJ0gkBHRe2EIZaIiEIlBs1qpQzHrjb8OnbVgl0pe187q2hVMpmGpnt/5TbblUHcrUvV4K5pWn3zn8WFXTqjCdWH3ylERBQqv0v+c82XnI8UiDRN2ekE083/hYVOxcbP2bFt6bxVrcQC8tjKhZz0GPaJpWYxxBIRUah0w0BC3M2piaqkGIjMVBq6rt4q/Rp5QVvj5+w3f1jtECtMHamjEss5sVQvhlgiIgqddJm5iaqkXJFUcypBjbS4q4kFbf7ttRJzPDp69QR3uTcBQyzVhyGWiIhC14qWU2IlVtX5sDViyG6mQ4H4HFXba9X4nbM0fUCqxAY8KGobDLFERBQ6MXyJi5XqEZfOBDVicG/unL2VTNWrz+I5u44jbXrguo7ntsZoQnXidwoREYWuFTtYSSFW9UqscM621XibLWmbXcUrsX7THaTPmn1iqUkMsUREFDq5KlmE49h1P79qVeBUrXlfUzVmSm6z1WiPXLkSq/Y5Awvv0MY+sdQshlgiIgqdX/hqpBpbKXjDnF8LK9X4jbGRc3ZsG1VhxyvVp1AAfvNihcVd7E5ATWKIJSKi0OlGQm6zVai/5ZQYhMx0FloMws9i2mz5t9dSewoF4FN1XyC4x+FzJDUwxBIRUSRS2QUqdPMQK5jia6lKDJ3lRs5ZCPnT803V7Ytbs9A5u453YRcrsVQvhlgiIopEMtvpuV3Oy7s5zSWOc0MBINXhPeeKzw5WcxFbiomvpSq/+c+zgyv7xFKzGGKJiCgSqcVUJWPWaqpG7KBQKRbkSuQc5L64MQmxYpXcdb2fNfvEUpMYYomIKBJSoCvk5Eb4PhzHjl17rZpUxhs8Xcepe6MHsWqbikmINRKmz/zn2ecidCfgdAKqE0MsERFFQgxhruPUtQGAX/UyNoHONJFIpjzHxAqrH9dxfOYBx+OcASDV0eW5XZo1dUT+u4UhlurDEEtERJFIJFMwzKTnWCk/teDzysJjzHQGRsJs6diCJM5lrWcucKUU3+AO+CziK8wOscKOXazEUp0YYomIKDLpTm+Frp5AJz4mTmEO8FvQVk9w957z9B8AcQruYiV21jmzTyw1iSGWiIgik8qKIbaOQBfTVfo16fkC3RxKuUnPbTEUqk78Q8MqFuDY/ju0sRJL9WKIJSKiyKQ6Gw90YtAVg7Dq0p3dnttWsQBb2EJXJH5dxNdQXSrbKVVYS/npYF7PYj4iPwyxREQUGbEquVCgq1oVWKWi9zViFuiS2Q5ouvfX70LTKMpCJVachqE63TCkamwp92Ew53QCahJDLBERRcYv0ImXzj33TU14bmuGEZv2WjW6bkh9beerQFcrZVQrZc+xuAV3QB5z7XMWK7GcTkD1YoglIqLI6LpPhW5qnhArViQ7umIZeqRAJ4Rzz33COWuGATMdjx3KZpsrxLJPLDWLIZaIiCKV7urx3C7m6g904nPjQjrnyYtzPla8L93ZHcugJ4bYSiEHx7bZJ5aaxhBLRESRygiBbr6qpF+giyPxnCvFPKpWxfexxamL8z43LtKdXZ75rq7jTP9Rwm1nqUkMsUREFCmxKmmVitIcUGB6py7xeLZ7WZBDC0y6o0ueC+wT3l3XRXHSezwT03PWjYTUGqwwOQ5XnE7AFEt1YoglIqJIpbKd0IUdtwoTY9LjCpPjnttGMiUtkIoLTdfrmlJQLuTg2FXPsWz38iCHFijxj47ixLhUiRXDPdFc+J1CRESR0jRNCjeFiXHpcUXhWFyrsDXitIC8X3AXztlMZ5BIpgIdV5CyPd4AXpy6KG2nS1QvhlgiIopcdtkKz+38RTnQiSFPDERx07Fsped2cfKi1CM3Pz7quZ3t8X6d4iYjVJFty5KnjsRw0RpFgyGWiIgiJ4azSiHn2dSgXMjBKhbmfU7cZHtWeC+du66n8uo6DgpCmO9Y7g2+cWOm0khm598mmHNiqV4MsUREFLl0ZzcM0zsvdurC+Zn/nxsb8dyXSKZi25mgRjcMqTI5+zwLk+PSfNiO5b2hjC1InQudAzMs1YkhloiIIqdpGjpXrPIcmxo999H/nxVoAaBzpfexcSVWVqdGz83MEZ08f9ZzX7qzGwkzGdrYgtK5om/e+zWN0YTqw+8UIiJSQldvv+d2YXIcVqmIciEnLepaKAjFRXffas9t26ogf/ECHMfG5KwQDwBdwmPjKtOzTOpG4cFKLNUpNiH20UcfxW/8xm8gm81i2bJlUQ+HiIharGPZSuhG4qMDroux0ycwfnbI8zjDTMZ+bmhNMp2V+r6OnT6OyfNn4QiLvHr6LglxZMHRdUMK77NxTizVKzYhtlKp4Pd///fx5S9/OeqhEBFRAHTDQM/qNZ5jY6ePY/z0Cc+xZavXQNeNMIcWqJ5VA57b+fELOPveQc+x7LKVMNOZMIcVqGWr1859J7sTUJ1iE2K//e1v42tf+xquvfbaqIdCREQBWbFm/fwhRtOwbPVgeAMKQU//mgV7v65cuyGcwYQk09Uz585jGkMs1Sk2IbYZ5XIZk5OTnv+IiEhdyXR23irdyrUbkMxkQxxR8HTDQO+6y+a8v2P5yraZAzxb/2VX+d/BEEt1ausQ+9hjj6Gnp2fmv8HB9vrrnYioHfVvvBIpn/ZZ6c5u9K67PIIRBW/ZJYO+C7cSqTRWb9ocwYiCl+nqkcK7pmltNW2CghVpiH3wwQehadq8/x05cqTp13/ooYcwMTEx89/Q0NDCTyIiokjphoH1W3Zh+cA6GGYSmmFg+Zr1WLdlF3SjfebCzqZpGtZcsQUrBy+Fmc5AT5joWNGHDVv3IJlur8rzbH0bNmHgii0wM1noCRPLLxls28+YWi+x8EOC88d//Me4++67533Mxo0bm379VCqFVCq+e0wTES1VRsLE6suvxurLr4bruktinqSm61h16RVYdekVUQ8lVD39A+js7cdEef6dvIhEkYbYvr4+9PW13zwfIiJqnaUQYImocZGG2EacPHkSY2NjOHnyJGzbxoEDBwAAl19+OTo7+dcbERER0VISmxD78MMP45//+Z9nbm/fvh0A8N///d+44YYbIhoVEREREUUhNt0Jnn76abiuK/3HAEtERES09MQmxBIRERER1cRmOkEruK4LANz0YAmzbRu5fA7A9PeBwVYuyuBnoyZ+LhQGfp+pK4rPppbTarltLksqxE5NTQEANz0gIiIiUtzU1BR6enrmvF9zF4q5bcRxHJw5cwZdXV2htGyZnJzE4OAghoaG0N0t7z5DRF78mSFqDH9mqB25roupqSkMDAxA1+ee+bqkKrG6rmPt2rn35A5Kd3c3/3EhagB/Zogaw58ZajfzVWBruLCLiIiIiGKHIZaIiIiIYochNkCpVArf/OY3kUqloh4KUSzwZ4aoMfyZoaVsSS3sIiIiIqL2wEosEREREcUOQywRERERxQ5DLBERERHFDkMsEREREcUOQ2xAvv/972PDhg1Ip9PYs2cPXnvttaiHRKSsxx57DLt27UJXVxdWrVqF22+/He+++27UwyKKhb/8y7+Epmn46le/GvVQiELFEBuA5557Dnv37sU3v/lN7N+/H1u3bsUtt9yC8+fPRz00IiX9/Oc/x/33349XX30VP/3pT2FZFm6++Wbk8/moh0aktNdffx0/+MEPsGXLlqiHQhQ6ttgKwJ49e7Br1y5873vfAwA4joPBwUH84R/+IR588MGIR0ekvpGREaxatQo///nP8clPfjLq4RApKZfLYceOHfi7v/s7PPLII9i2bRsef/zxqIdFFBpWYlusUqlg3759uOmmm2aO6bqOm266Cb/85S8jHBlRfExMTAAAVqxYEfFIiNR1//3347bbbvP8viFaShJRD6DdjI6OwrZt9Pf3e4739/fjyJEjEY2KKD4cx8FXv/pVfOITn8A111wT9XCIlPTss89i//79eP3116MeClFkGGKJSCn3338/Dh48iF/84hdRD4VISUNDQ/ijP/oj/PSnP0U6nY56OESRYYhtsd7eXhiGgXPnznmOnzt3DqtXr45oVETx8MADD+AnP/kJXn75Zaxduzbq4RApad++fTh//jx27Ngxc8y2bbz88sv43ve+h3K5DMMwIhwhUTg4J7bFkskkdu7ciZdeemnmmOM4eOmll/Dxj388wpERqct1XTzwwAP48Y9/jJ/97Ge49NJLox4SkbJuvPFGvP322zhw4MDMf9dddx3uvPNOHDhwgAGWlgxWYgOwd+9e3HXXXbjuuuuwe/duPP7448jn87jnnnuiHhqRku6//34888wzeP7559HV1YXh4WEAQE9PDzKZTMSjI1JLV1eXNF+8o6MDK1eu5DxyWlIYYgPw2c9+FiMjI3j44YcxPDyMbdu24cUXX5QWexHRtL//+78HANxwww2e40899RTuvvvu8AdERETKY59YIiIiIoodzoklIiIiothhiCUiIiKi2GGIJSIiIqLYYYglIiIiothhiCUiIiKi2GGIJSIiIqLYYYglIiIiothhiCUiIiKi2GGIJSKKgQsXLmDVqlU4fvz4vI8bHR3FqlWrcOrUqXAGRkQUEe7YRUQUA3v37sXU1BT+8R//ccHHfv3rX8f4+Dj+6Z/+KYSRERFFgyGWiEhxhUIBl1xyCf7rv/4L119//YKPP3ToEHbu3IkzZ85gxYoVIYyQiCh8nE5ARKS4//iP/0AqlZoJsOPj47jzzjvR19eHTCaDTZs24amnnpp5/ObNmzEwMIAf//jHUQ2ZiChwiagHQERE83vllVewc+fOmdt/9md/hsOHD+M///M/0dvbi/fffx/FYtHznN27d+OVV17BF77whbCHS0QUCoZYIiLFnThxAgMDAzO3T548ie3bt+O6664DAGzYsEF6zsDAAN58882whkhEFDpOJyAiUlyxWEQ6nZ65/eUvfxnPPvsstm3bhj/90z/F//zP/0jPyWQyKBQKYQ6TiChUDLFERIrr7e3F+Pj4zO1bb70VJ06cwNe+9jWcOXMGN954I77+9a97njM2Noa+vr6wh0pEFBqGWCIixW3fvh2HDx/2HOvr68Ndd92FH/7wh3j88cfxD//wD577Dx48iO3bt4c5TCKiUDHEEhEp7pZbbsGhQ4dmqrEPP/wwnn/+ebz//vs4dOgQfvKTn+Cqq66aeXyhUMC+fftw8803RzVkIqLAMcQSESnu2muvxY4dO/CjH/0IAJBMJvHQQw9hy5Yt+OQnPwnDMPDss8/OPP7555/HunXr8Ju/+ZtRDZmIKHDc7ICIKAZeeOEF/Mmf/AkOHjwIXZ+//nD99dfjK1/5Cj7/+c+HNDoiovCxxRYRUQzcdtttOHr0KE6fPo3BwcE5Hzc6OorPfOYz+NznPhfi6IiIwsdKLBERERHFDufEEhEREVHsMMQSERERUewwxBIRERFR7DDEEhEREVHsMMQSERERUewwxBIRERFR7DDEEhEREVHsMMQSERERUewwxBIRERFR7Px/PW3lULPXKZEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Relative time marker specification\n", + "\n", + "bp_rtm = bb.BluePrint()\n", + "bp_rtm.setSR(100)\n", + "bp_rtm.insertSegment(0, ramp, (0, 1), dur=1)\n", + "bp_rtm.insertSegment(1, ramp, (1, 1), dur=1)\n", + "bp_rtm.insertSegment(\n", + " 2, sine, (1.675, 1, 0, np.pi / 2), dur=1.5, name=\"mysine\"\n", + ") # This is the important segment\n", + "# make marker 1 go ON a bit before the sine comes on\n", + "bp_rtm.setSegmentMarker(\n", + " \"mysine\", (-0.1, 0.2), 1\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "bp_rtm.setSegmentMarker(\"mysine\", (0.75, 0.1), 2)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "# Even if we insert segments before and after the sine, the markers \"stick\" to the sine segment\n", + "bp_rtm.insertSegment(0, ramp, (0, 0), dur=1)\n", + "bp_rtm.insertSegment(-1, ramp, (0, 0.2), dur=1)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "\n", + "# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn\n", + "# markers ON. It is up to the user to ensure that markers switch off again as expected, i.e. that different marker\n", + "# specifications do not overlap." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modifying blueprints\n", + "([back to ToC](#Table-of-Contents))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.106221Z", + "iopub.status.busy": "2025-01-20T06:27:57.105869Z", + "iopub.status.idle": "2025-01-20T06:27:57.288050Z", + "shell.execute_reply": "2025-01-20T06:27:57.287470Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArMAAAEaCAYAAAACHN+gAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAHk5JREFUeJzt3X1M1efdx/HPOViBTeGuU0AUxdxydz5UQVSky2pnqcy5Zq63jelMZNaauGi3lnRdXTZdtqQsM7Zs0VXbTc3inHYPamJbG8PiQyOLSsudqqvT6NQWYbIyQHyC8zv3H65n5ZzjA/7OOdfv4f1KSMrxd+BLc8Hvc67zva4rEA6HwwIAAABcKGi6AAAAAOBuEWYBAADgWoRZAAAAuBZhFgAAAK5FmAUAAIBrEWYBAADgWoRZAAAAuBZhFgAAAK7Vz3QBqWZZlpqamjRw4EAFAgHT5QAAACBKOBxWZ2en8vPzFQzeeu7Vd2G2qalJBQUFpssAAADAbZw/f17Dhw+/5TW+C7MDBw6UdON/TlZWluFqAO8IhUJq/Mv/SZKKp01UWlqa4YqA5GG8w09MjPeOjg4VFBREctut+C7MftJakJWVRZgFEigUCmnAZwdIuvH7xc0dXsZ4h5+YHO930hLKAjAAAAC4FmEWAAAArkWYBQAAgGsRZgEAAOBavlsAlmptJ0/K6u42XQaQdJZlqeP8eUnSx3/NuO2+gICbMd7hJ5Zl6VJTkwbk55suJS7CbJJZ3d2yenpMlwEknWVZCodCN/67p0fi5g4PY7zDTyzLkvXv8e5EhNkkC95zj+kSgNSwLAX+vV1LsF8/ZqrgbYx3+IllKejg7ecIs0l2b1GR6RKAlAiFQsr6+KokadCYMey7CU9jvMNPQqGQBvx7vDsRLyUBAADgWoRZAAAAuBZhFgAAAK5FmAUAAIBrEWYBAADgWuxmkGQnT7apu9syXQaQdJZl6dz5TklS5l8/ZqsieBrjHX5iWZaami4pP3+A6VLiIswmWXe3pZ4ewiy878am2jfGek+PxR7y8DTGO/zEsiz1hMKmy7gpwmyS3XMPf+HgD5YlBdNujPd+/YLMVMHTGO/wE8uS+qUFTJdxU4TZJCsqutd0CUBKhEIhXfl4oCRpzJhBbCIPT2O8w09ujHdnthhILAADAACAixFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAaxFmAQAA4FqEWQAAALiW0TC7f/9+Pfroo8rPz1cgENCOHTtu+5y9e/dq0qRJSk9P1+jRo7Vp06ak1wkAAABn6mfym3d1dWnixIl68skn9dhjj932+jNnzmj27NlasmSJfvvb36qurk5PPfWUhg4dqsrKyhRUDOBWQj3dun65S52tLQqm8cYPvCugNFmWpWCQcQ6YZjTMzpo1S7Nmzbrj69etW6dRo0Zp9erVkqQxY8bonXfe0csvv0yYBQy7/K+PdeFvRxUOh/XRgOvc5OFplmWp5XSrckf9j+lSAN9z1d2mvr5eFRUVvR6rrKxUfX39TZ9z7do1dXR09PoAkHhtF84pHA6bLgNImZ5rV3XlEvcUwDRXhdnm5mbl5ub2eiw3N1cdHR26cuVK3OfU1NQoOzs78lFQUJCKUgHf6em+broEIOWsnh7TJQC+Z7TNIBWWL1+u6urqyOcdHR0EWiAZomZl0/qnK63fPYaKAZKj+9oVhUMh02UA+BRXhdm8vDy1tLT0eqylpUVZWVnKzMyM+5z09HSlp6enojwAn5JTWKT/yhtuugwgoc69f0Rdba2Rz2mtAcxzVZtBeXm56urqej22Z88elZeXG6oIwCe4qcMPAoGA6RIARDEaZi9duqTGxkY1NjZKurH1VmNjo86dOyfpRovAggULItcvWbJEp0+f1vPPP68PPvhAv/zlL/X666/r2WefNVE+gE+LDrPc9OELvIgDTDMaZo8cOaKSkhKVlJRIkqqrq1VSUqIVK1ZIki5cuBAJtpI0atQovfHGG9qzZ48mTpyo1atX61e/+hXbcgEOEH1LD4gwC+8JRG85xzsSgHFGe2YfeuihW741Ge90r4ceekjvvfdeEqsCcFdiZmbNlAEA8BdX9cwCcLLeYTYQ4M8LPCiqfYZeccA87jYAEoJ7Ovwgun2GYQ+YR5gFkBDhsNXrc1Z9w5OihzWv4gDjCLMAkoMwCw/iRRrgPIRZAIkRju6Z5aYPL4oa18zMAsYRZgEkBAth4Au8SAMchzALIDE4NAE+ENMyyxIwwDjCLICE4NAE+EHsoQlm6gDwH4RZAInBoQkAAAMIswAShAVg8IHoQxMs6yYXAkgVwiyAhIhd/0WYhffQPgM4D2EWQGKwNRf8IGZY0zQLmEaYBZAQMau6CbPwIF6kAc5DmAWQGMzMwheiembZXxkwjjALICG4qcMXol+kMewB4wizABKDQxPgAxyaADgPYRZAUrDqG14Uc2gCAOP4rQRgW9y9NsmyAIAUIMwCsC3eW60sAIMncWgC4DiEWQD2xW0bJMzCe2ifAZyHMAsgAZiZhU8wrAHHIcwCsI1tueAXsS/SGPuAaYRZAPbFuZ+z6hveFH1ogqEyAERwtwFgG3ttwjdiDk1g7AOmEWYB2Bfvhk7PLDyIJgPAeQizAJKCVd/wJGZmAcchzAKwLe4CMLIsPIhdOgDnIcwCsC9OmOWmD09iNwPAcQizAGyLvzUXYRY+QJsBYBxhFkAC0GYAf4h+x4EsC5hHmAVgW7yZWRaAwYtonwGchzALwD4OTYBvRM3M0jMLGMfdBoBt3NDhGzHrvxj7gGmEWQD2Rd/QeSsWHkX7DOA8hFkACUdfITyLQxMAxyHMArAtZgEYYRYexQs1wHkIswDsY3YKPkW/OGAeYRaAbdEzs4EAf1rgUTFtBmbKAPAf3HEAJBzvxMKrYg5NMFQHgP8gzAKwLRy2oh4hzcKbYnpmabEBjCPMArAv+n7O1Cw8K/o4W8IsYBphFoBt0YtgiLLwrJjBTZgFTCPMArCPrbngExyaADgPYRZAwrEXJzyLnlnAcQizAGyjbxB+wQs1wHkIswDso80APsULOcA8wiwA22IWgHFoAryKF2qA43DHAWAfE7PwiXhtBkzOAmYRZgHYxqEJ8DXSLGAUYRaAfRyaAJ+I10IT3WYDILUIswBs49AE+Ea8wU2WBYwizAKwj90M4BPxD00gzQImEWYBJBx7ccKz4i4AI8wCJhFmAdjGzRx+EX83A8Y/YBJhFoB9tBkAAAwhzAKwLXYzA/60wKPivVBjZhYwijsOAPu4mcMn4i4AY/wDRhFmAdgWfWgCC8DgWXE3MyDMAiYRZgEkHmEWHsWhCYDzEGYB2Ba9mpsoC89im1nAcQizAOxjNwP4BIcmAM7Tz86Tu7u71dzcrMuXL2vIkCEaNGhQouoC4GL0zMKz2GcWcJw+z8x2dnbqlVde0fTp05WVlaXCwkKNGTNGQ4YM0ciRI7V48WIdPnw4GbUCcChu5vCLuC/UGP6AUX0Ksy+99JIKCwu1ceNGVVRUaMeOHWpsbNTf/vY31dfXa+XKlerp6dHMmTP15S9/WSdPnkxW3QCchDYD+BgLwACz+tRmcPjwYe3fv1/jxo2L++9Tp07Vk08+qXXr1mnjxo06cOCAioqKElIoAOeKOTSBJWDwsEAwKFmf2o6OdyYAo/oUZn/3u9/d0XXp6elasmTJXRUEwIViZmbNlAEA8J8+98zOnTtXu3fvpkcOwKdEbc3Fcbbwsqg2mrBl3eRCAKnQ5ztOW1ubZs+erREjRmjFihU6ffp0MuoC4CK8uIWfRLfR0DMLmNXnMFtXV6fTp09r0aJF2rx5s4qKijRjxgxt2bJF165dS0aNABwu5tAEFoDBy6LHN1kWMOqu3gscOXKkfvSjH+n06dPas2eP8vPztXjxYg0dOlRLly5VQ0NDousE4CaEWXhY7PAmzQIm2W5smzFjhjZv3qzm5mbV1NRo69atKisrS0RtANyCmVn4SlSbAW02gFG2TgD7xJkzZ7Rp0yZt2rRJ7e3tqqioSMSXBeAS3MzhK7QZAI5y1zOzV69e1ebNmzVjxgwVFRXpN7/5jRYtWqQzZ85o9+7diawRgNNxaAJ8JHp0swAMMKvPM7OHDh3Shg0btG3bNl29elVf//rXtXv3bj388MO8tQj4FIcmwE8Cwah5IN6ZAIzqc5idNm2aJk6cqJ/85CeaP3++7r333mTUBcBNODQBAGBIn8LsuXPndOTIEU2aNOmOrv/oo480bNiwuyoMgJtELQAjzcLLODQBcJQ+9cxOmTJF69ev1+HDh296TXt7u1577TWNHz9ef/zjH+/o665du1aFhYXKyMhQWVmZDh06dNNrN23apEAg0OsjIyOjLz8GgASLXQBGmIV3cWgC4Cx9mpk9fvy4XnzxRT3yyCPKyMhQaWmp8vPzlZGRoba2Nh0/flzHjh3TpEmT9LOf/Uxf+cpXbvs1t23bpurqaq1bt05lZWWqra1VZWWlTpw4oZycnLjPycrK0okTJyKf06sLGBaz/ovfSXgYuxkAjtKnmdnPfe5zWr16tS5cuKA1a9aoqKhIra2tOnnypCRp/vz5amhoUH19/R0FWUl66aWXtHjxYi1cuFBjx47VunXr9JnPfEYbNmy46XMCgYDy8vIiH7m5uX35MQAkWMzMFGEWHsahCYCz3NU+s5mZmZo7d67mzp1r65tfv35dDQ0NWr58eeSxYDCoiooK1dfX3/R5ly5d0siRI2VZliZNmqQXX3xR48aNi3vttWvXeh2z29HRYatmAHFwaAJ8hUMTACexfQKYHa2trQqFQjEzq7m5uWpubo77nPvuu08bNmzQzp07tXnzZlmWpQceeEAffvhh3OtramqUnZ0d+SgoKEj4zwH4HTdz+AptBoCjGA2zd6O8vFwLFixQcXGxpk+frj/96U8aMmSI1q9fH/f65cuXq729PfJx/vz5FFcM+ACHJsBHODQBcJaEHGd7twYPHqy0tDS1tLT0erylpUV5eXl39DXuuecelZSU6NSpU3H/PT09Xenp6bZrBXBzHJoAP+HQBMBZjM7M9u/fX6Wlpaqrq4s8ZlmW6urqVF5efkdfIxQK6f3339fQoUOTVSaA2+HQBACAIUZnZiWpurpaVVVVmjx5sqZOnara2lp1dXVp4cKFkqQFCxZo2LBhqqmpkST9+Mc/1rRp0zR69Gj961//0qpVq3T27Fk99dRTJn8MwOdYAAYf4dAEwFGMh9l58+bp4sWLWrFihZqbm1VcXKzdu3dHFoWdO3dOwU+9pdPW1qbFixerublZ9957r0pLS3Xw4EGNHTvW1I8A+F7su6yEWXgXbTSAsxgPs5K0bNkyLVu2LO6/7d27t9fnL7/8sl5++eUUVAXgjrE1F/wkemaWnlnAKNftZgDAeVjNDT/h0ATAWQizAOyLnpmNXu0NeAozs4CTcMcBAKAvODQBcBTCLADbYlZz0zMLD+PQBMBZCLMAbOPQBPhKzMwsYRYwiTALwD4OTYCPsFsH4CyEWQAJwNZc8BG25gIchTALwDYOTYCfxLTREGYBowizAOyjzQB+EpNlCbOASYRZALZFr+ZmARi8LLaNhjALmESYBWAfhybAV6J6ZgmzgFHccQAA6AsOTQAchTALwDYOTYCfcGgC4CyEWQC2cWgCfIVDEwBHIcwCsI/dDOAj7KMMOAthFkACcGgC/IutuQCzCLMAbOPQBPgKbQaAoxBmAdhHmwF8JPqdB6IsYBZhFoBtHJoAP4lpo2FmFjCKMAvAvpiZWcIsvCxqZjZs3eQ6AKlAmAWQcCwAg6dxaALgKIRZALbFrOYmzMLDODQBcBbCLAD7wvTMwkfomQUchTALwJa4e2ySZeFhtNEAzkKYBWBPnDDLzR5+wqEJgFmEWQC2xO8XJMzCw2gzAByFMAvAHrIsfIZDEwBnIcwCsCXeHpssAIOvMDMLGEWYBWBP3JlZwiy8KxDofeukZxYwizALwJa4PbOEWXgZ4xtwFMIsAHvibs3FzR7eFXNoAsfZAkYRZgEA6At2MwAchTALwJZ4/YLsMwsvY3wDzkKYBWAPYRY+xwIwwCzCLABbODQBvkObAeAohFkA9pBl4TPR+ygTZQGzCLMAbOHQBPhO7HYGRsoAcANhFkDi0TMLD4s+NIG5WcAswiwAW+LvZmCgECBVaJkFHIUwC8Ae7uTwmZieWQ5NAIwizAJIKLblguexmwHgKIRZALawxyb8hhdsgLMQZgHYEx1mudHDZ3hBB5hFmAVgS/xDEwAPo80AcBTCLAB7Yu7jzMzC2zg0AXAWwiwAm3rfyuknhOdxaALgKIRZALbE9AsSZuFxHJoAOAthFoAt0WGWKAu/YWIWMIswC8CemJlZM2UAqRLdSsOhCYBZhFkACRW9OAbwHFppAEchzAKwhT024Tcxixz5HQCMIswCsIcFYPA5XtABZhFmAdjCoQnwHWZmAUchzAKwJ/o+zswsPI6+cMBZCLMAbGJrLvhMnEEettjRADCFMAvAlth+QeIsvC3eKXe02wDmEGYB2MP6L/hOvKnZ1FcB4AbCLABbYmakSLPwuHgzs6RZwBzCLAB7aDMA2J4LMIgwC8AWbuLwm0Awzq2TXwPAGMIsAHs4NAFgARhgEGEWgC00GcB34r1g4x0KwBjCLAB7mJmFz3BoAuAshFkANkUdmsB9Hl7HoQmAoxBmAdjCoQnwm/hbcwEwhTALwB5aBeE7cU4Ao2cWMIYwC8AWVnHDd+JOzPJ7AJhCmAVgT9SMVNw9OAEPibcAjJlZwBzuOgAA9AGHJgDOQpgFYAuruAHabQCTCLMAbIndy4CV3vCB6B0NaDMAjCHMArAn5tAEM2UAqcT2XIBzEGYB2BR9aAI3efhA1DhnARhgDmEWgC3cxOFHMe00/B4AxhBmAdgTcw9nZhY+wMws4BiEWQC2sIobfhTbTcPvAWAKYRaAPRyaAF9iZhZwCkfcddauXavCwkJlZGSorKxMhw4duuX1v//97/X5z39eGRkZuv/++/Xmm2+mqFIAABRnay4zZQCQ+pkuYNu2baqurta6detUVlam2tpaVVZW6sSJE8rJyYm5/uDBg3riiSdUU1Ojr371q9qyZYvmzJmjd999V+PHjzfwE9xcT/d1hUMh02UASRXq6e79AC2z8IHoYd5z/Zq6r14xUguQbFbIUs/16wr2Mx4b4wqEDb83UlZWpilTpmjNmjWSJMuyVFBQoKefflovvPBCzPXz5s1TV1eXdu3aFXls2rRpKi4u1rp16277/To6OpSdna329nZlZWUl7geJo+nE+2pv+Sip3wNwCsuydOKvH2rg53I0438fVVpamumSgKQ5Uf9nHWs8JUm6b8xwBWmvgYd98vd98Ij/1he+/FBK/r73Ja8Z/e27fv26GhoaVFFREXksGAyqoqJC9fX1cZ9TX1/f63pJqqysvOn1165dU0dHR68PAADsYD9lwDmMhtnW1laFQiHl5ub2ejw3N1fNzc1xn9Pc3Nyn62tqapSdnR35KCgoSEzxAOLql55hugQg6fpnftZ0CQD+zfPviyxfvlzt7e2Rj/Pnz5suCfCmQECZWf+lz2QPMl0JkHQ5o+7jhRvgEEY7eQcPHqy0tDS1tLT0erylpUV5eXlxn5OXl9en69PT05Wenp6Ygvsor2is8kaPMfK9gVQLWWFdrv8/02UAKZH+2QEaOnqsLMvS/zxQrLQ0z88NwcdCIUuX9J4CAWeOc6Nhtn///iotLVVdXZ3mzJkj6UaTcV1dnZYtWxb3OeXl5aqrq9MzzzwTeWzPnj0qLy9PQcV9EwyyAAb+ERY7d8B/gsGggmlpCrLgER4WVsjRmcb4HgvV1dWqqqrS5MmTNXXqVNXW1qqrq0sLFy6UJC1YsEDDhg1TTU2NJOk73/mOpk+frtWrV2v27NnaunWrjhw5oldffdXkjwEAAAADjIfZefPm6eLFi1qxYoWam5tVXFys3bt3RxZ5nTt3rteWJw888IC2bNmiH/zgB/r+97+voqIi7dixw3F7zAIAACD5jIdZSVq2bNlN2wr27t0b89jjjz+uxx9/PMlVAQAAwOmc2ckLAAAA3AFHzMym0icHnnF4ApBYoVBIl7ouSbrx+8UJYPAyxjv8xMR4/ySn3clBtb4Ls52dnZLE4QkAAAAO19nZqezs7FteEwjfSeT1EMuy1NTUpIEDB6bkOMKOjg4VFBTo/Pnztz1bGHA7xjv8hPEOP0n1eA+Hw+rs7FR+fn6vjQDi8d3MbDAY1PDhw1P+fbOysvhjB99gvMNPGO/wk1SO99vNyH6CBWAAAABwLcIsAAAAXIswm2Tp6elauXKl0tPTTZcCJB3jHX7CeIefOHm8+24BGAAAALyDmVkAAAC4FmEWAAAArkWYBQAAgGsRZgEAAOBahNkkW7t2rQoLC5WRkaGysjIdOnTIdElAwu3fv1+PPvqo8vPzFQgEtGPHDtMlAUlTU1OjKVOmaODAgcrJydGcOXN04sQJ02UBSfHKK69owoQJkcMSysvL9dZbb5kuqxfCbBJt27ZN1dXVWrlypd59911NnDhRlZWV+sc//mG6NCChurq6NHHiRK1du9Z0KUDS7du3T0uXLtVf/vIX7dmzR93d3Zo5c6a6urpMlwYk3PDhw/XTn/5UDQ0NOnLkiGbMmKGvfe1rOnbsmOnSItiaK4nKyso0ZcoUrVmzRpJkWZYKCgr09NNP64UXXjBcHZAcgUBA27dv15w5c0yXAqTExYsXlZOTo3379unBBx80XQ6QdIMGDdKqVau0aNEi06VIYmY2aa5fv66GhgZVVFREHgsGg6qoqFB9fb3BygAAidTe3i7pxg0e8LJQKKStW7eqq6tL5eXlpsuJ6Ge6AK9qbW1VKBRSbm5ur8dzc3P1wQcfGKoKAJBIlmXpmWee0Re+8AWNHz/edDlAUrz//vsqLy/X1atXNWDAAG3fvl1jx441XVYEYRYAgLu0dOlSHT16VO+8847pUoCkue+++9TY2Kj29nb94Q9/UFVVlfbt2+eYQEuYTZLBgwcrLS1NLS0tvR5vaWlRXl6eoaoAAImybNky7dq1S/v379fw4cNNlwMkTf/+/TV69GhJUmlpqQ4fPqyf//znWr9+veHKbqBnNkn69++v0tJS1dXVRR6zLEt1dXWO6jMBAPRNOBzWsmXLtH37dv35z3/WqFGjTJcEpJRlWbp27ZrpMiKYmU2i6upqVVVVafLkyZo6dapqa2vV1dWlhQsXmi4NSKhLly7p1KlTkc/PnDmjxsZGDRo0SCNGjDBYGZB4S5cu1ZYtW7Rz504NHDhQzc3NkqTs7GxlZmYarg5IrOXLl2vWrFkaMWKEOjs7tWXLFu3du1dvv/226dIi2JorydasWaNVq1apublZxcXF+sUvfqGysjLTZQEJtXfvXn3pS1+KebyqqkqbNm1KfUFAEgUCgbiPb9y4Ud/85jdTWwyQZIsWLVJdXZ0uXLig7OxsTZgwQd/73vf0yCOPmC4tgjALAAAA16JnFgAAAK5FmAUAAIBrEWYBAADgWoRZAAAAuBZhFgAAAK5FmAUAAIBrEWYBAADgWoRZAAAAuBZhFgBc5J///KdycnL097///ZbXtba2KicnRx9++GFqCgMAQzgBDABcpLq6Wp2dnXrttddue+1zzz2ntrY2/frXv05BZQBgBmEWAFzi8uXLGjp0qN5++21NmzbtttcfO3ZMpaWlampq0qBBg1JQIQCkHm0GAOASb775ptLT0yNBtq2tTfPnz9eQIUOUmZmpoqIibdy4MXL9uHHjlJ+fr+3bt5sqGQCSrp/pAgAAd+bAgQMqLS2NfP7DH/5Qx48f11tvvaXBgwfr1KlTunLlSq/nTJ06VQcOHNCiRYtSXS4ApARhFgBc4uzZs8rPz498fu7cOZWUlGjy5MmSpMLCwpjn5Ofn67333ktViQCQcrQZAIBLXLlyRRkZGZHPv/Wtb2nr1q0qLi7W888/r4MHD8Y8JzMzU5cvX05lmQCQUoRZAHCJwYMHq62tLfL5rFmzdPbsWT377LNqamrSww8/rOeee67Xcz7++GMNGTIk1aUCQMoQZgHAJUpKSnT8+PFejw0ZMkRVVVXavHmzamtr9eqrr/b696NHj6qkpCSVZQJAShFmAcAlKisrdezYscjs7IoVK7Rz506dOnVKx44d065duzRmzJjI9ZcvX1ZDQ4NmzpxpqmQASDrCLAC4xP33369Jkybp9ddflyT1799fy5cv14QJE/Tggw8qLS1NW7dujVy/c+dOjRgxQl/84hdNlQwAScehCQDgIm+88Ya++93v6ujRowoGbz0fMW3aNH3729/WN77xjRRVBwCpx9ZcAOAis2fP1smTJ/XRRx+poKDgpte1trbqscce0xNPPJHC6gAg9ZiZBQAAgGvRMwsAAADXIswCAADAtQizAAAAcC3CLAAAAFyLMAsAAADXIswCAADAtQizAAAAcC3CLAAAAFyLMAsAAADX+n/DW6bnWL3UXwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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v/M7cfNHHnkEL7/8cvIDESWQTqfbcf7SSy/hD//wD5MbhijBHnvsMZw5cwYzMzMoKChAW1sb/vzP/xwf//jHRUfbwjJLRERERGmL18wSERERUdpimSUiIiKitMUyS0RERERpi2WWiIiIiNIWyywRERERpS2WWSIiIiJKWyyzRERERJS2WGaJiIiIKG2xzBIRpZHFxUWUl5djYmLilo9bWFhAeXk5JicnkxOMiEgQ7gBGRJRGTp48idXVVfzgBz+47WO/+tWvwufz4V/+5V+SkIyISAyWWSKiNLG2toaqqir87Gc/w3333Xfbx4+OjqKjowPT09MoLi5OQkIiouTjZQZERGniJz/5CUwm01aR9fl8+MIXvoCysjJYLBbU19fjpZde2np8c3MzbDYb3njjDVGRiYgSTi86ABER3Zlf/OIX6Ojo2Pr1X//1X2NsbAw//elPUVpaiuvXryMUCmme093djV/84hd47LHHkh2XiCgpWGaJiNKEy+WCzWbb+rXb7UZ7ezs6OzsBAHV1dXHPsdlsuHDhQrIiEhElHS8zICJKE6FQCGazeevXf/RHf4TXXnsNR44cwde//nX86le/inuOxWLB2tpaMmMSESUVyywRUZooLS2Fz+fb+vUDDzwAl8uFr3zlK5iensbHPvYxfPWrX9U8Z2lpCWVlZcmOSkSUNCyzRERpor29HWNjY5pZWVkZHnnkEbzyyit49tln8f3vf19zfGRkBO3t7cmMSUSUVCyzRERp4sSJExgdHd06O/vUU0/hrbfewvXr1zE6Ooof/ehHaGxs3Hr82toaBgYG8IlPfEJUZCKihGOZJSJKE62trTh69Ch++MMfAgCMRiNOnTqFtrY2fPjDH4Ysy3jttde2Hv/WW2/B4XDgt3/7t0VFJiJKOG6aQESURn784x/ja1/7GkZGRiBJtz4fcd999+FP/uRP8Ad/8AdJSkdElHxcmouIKI08+OCDuHbtGqampmC323d93MLCAj796U/j85//fBLTERElH8/MEhEREVHa4jWzRERERJS2WGaJiIiIKG2xzBIRERFR2mKZJSIiIqK0xTJLRERERGmLZZaIiIiI0hbLLBERERGlLZZZIiIiIkpbLLNERERElLb+P/nXN65/l430AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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167V448/ro8++mglsiLFDfV2aain0zJLd7lVUdfAwhcApLjM3PxFF8JmQtOGUmE1LKkBPP/886qurtbrr7+u3bt365133lFzc7POnj2rw4cP65lnntHMzIzuuecefelLX1J7e/tK5UaKGQ8OKXC+1TKzfXLWYNq8swYBAKkpv7xKeeVVlllockK+1uMxexZIHmlLufFHH32k9957T5s3b17w92+//XZ97Wtf08svv6zXX39d77//vmpqapYlKFJXaGpSvpbYha/yms0xnwIDAEhtpbfUampsRBPBK3Oz8SsDunzxrEpv4QzyZLSkMvvv//7v13U7p9OpJ5544oYCAZ8WiYTla23WzPSUZV5YtU65JV5DqQAA8cpun33X7uLRDyzfOwZ9HXJle5RbyveOZLPkCw0feOABHThwgPPbsCr851otP11LUlZ+kYqrWfgCACwsLcOpyvqmmH2K3vZTmhgZNpQKK2XJZXZoaEj33Xef1qxZo3379unChQsrkQvQUE+nhv3dllm6O1MVtQ2yzfsIQwAAPs3tyVPZhnrLLBqJzC6EzXu3D4ltyWX20KFDunDhgh577DG98cYbqqmp0V133aU333xTU1M8OLA8xocHFbjQZpldXfhypKcbSgUASCR5ZZXK966xzGamJhf84B0krhs6z2jt2rX6q7/6K124cEEHDx6U1+vV448/rvLycj355JM6cuTIcudECglNTqh7gYUv76atcmXlGEoFAEhEpetrlZlbYJlNDA/FvGCCxHXTh3PeddddeuONN+T3+7V//3699dZb2rlz53JkQwqKfHImYHjemYCFa26Rp6jMUCoAQKKy2e2qqGtQmtNlmQ/1dOrKvEvZkJiW5aT5ixcv6u///u/17LPPanh4WLt3716OPxYpyH/udMyntWQXFKt47QZDiQAAiS4tw6mqzdtjFsL851piloyReG64zE5OTuqNN97QXXfdpZqaGv3rv/6rHnvsMV28eFEHDhxYzoxIEYO+Dg0HeiyzDHeWvLXbWPgCANwUV7ZH5TVbLDMWwpLDks6ZlaQPP/xQr732mt5++21NTk7qD/7gD3TgwAHdfffdFA7csLGhAQUunLHM7I40VW5ukiONhS8AwM3LLfVqcjSoQV/H3GxmekrdLce0Ztttstsd5sLhhi25zN5xxx1qaGjQ97//fT300EPKz89fiVxI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fvnLX8bMvvKVr+grX/nKCqcCAABAvEv60wwAAACQvOLildnVFP3k07hW68MTgKUIh8MaHRuVNPsYdTgchhMBSDY8z2CpTDxmrva0aPTaH5+bcmV2ZGREklbtwxMAAABwY0ZGRpSbm/uZt7FFr6fyJpFIJKKenh7l5OSsygcYBINBVVVVqaurSx6PZ8X/PgCph+cZACtttZ9notGoRkZG5PV6LQcBLCTlXpm12+2qrKxc9b/X4/HwTQbAiuJ5BsBKW83nmWu9InsVC2AAAABIWJRZAAAAJCzK7ApzOp165pln5HQ6TUcBkKR4ngGw0uL5eSblFsAAAACQPHhlFgAAAAmLMgsAAICERZkFAABAwqLMAgAAIGFRZlfYSy+9pOrqarlcLu3cuVMffvih6UgAksT+/ft12223KScnRyUlJbr//vt15swZ07EAJKm//du/lc1m07e+9S3TUSwosyvo7bff1t69e/XMM8/o6NGjamho0J49e3T58mXT0QAkgV/96ld68skn9Zvf/EYHDx5UKBTSPffco7GxMdPRACSZjz76SK+88oq2bdtmOkoMjuZaQTt37tRtt92mF198UZIUiURUVVWlP/mTP9Gf/dmfGU4HINn09fWppKREv/rVr/SFL3zBdBwASWJ0dFTbt2/XD37wA/3N3/yNGhsb9cILL5iONYdXZlfI9PS0jhw5ot27d8/N7Ha7du/ercOHDxtMBiBZDQ8PS5IKCgoMJwGQTJ588kndd999lk4TT9JMB0hW/f39CofDKi0ttcxLS0vV1tZmKBWAZBWJRPStb31Ln//857VlyxbTcQAkibfeektHjx7VRx99ZDrKoiizAJAEnnzySZ06dUq//vWvTUcBkCS6urr0zW9+UwcPHpTL5TIdZ1GU2RVSVFQkh8OhQCBgmQcCAZWVlRlKBSAZPfXUU/rZz36m9957T5WVlabjAEgSR44c0eXLl7V9+/a5WTgc1nvvvacXX3xRU1NTcjgcBhPO4prZFZKRkaEdO3bo0KFDc7NIJKJDhw5p165dBpMBSBbRaFRPPfWUfvrTn+r//u//tG7dOtORACSRu+++WydPnlRzc/Pc16233qqHHnpIzc3NcVFkJV6ZXVF79+7VI488oltvvVW33367XnjhBY2NjenRRx81HQ1AEnjyySf15ptv6t1331VOTo78fr8kKTc3V26323A6AIkuJycn5hr8rKwsFRYWxtW1+ZTZFfTggw+qr69P+/btk9/vV2Njow4cOBCzFAYAN+KHP/yhJOnOO++0zF9//XX98R//8eoHAgADOGcWAAAACYtrZgEAAJCwKLMAAABIWJRZAAAAJCzKLAAAABIWZRYAAAAJizILAACAhEWZBQAAQMKizAIAACBhUWYBIIEMDAyopKREHR0dn3m7/v5+lZSUqLu7e3WCAYAhfAIYACSQvXv3amRkRK+++uo1b/ud73xHQ0ND+ud//udVSAYAZlBmASBBjI+Pq7y8XP/zP/+jO+6445q3P336tHbs2KGenh4VFBSsQkIAWH1cZgAACeIXv/iFnE7nXJEdGhrSQw89pOLiYrndbtXU1Oj111+fu/3mzZvl9Xr105/+1FRkAFhxaaYDAACuz/vvv68dO3bM/fov//Iv1dLSov/+7/9WUVGRzp07p4mJCct9br/9dr3//vt67LHHVjsuAKwKyiwAJIhLly7J6/XO/bqzs1NNTU269dZbJUnV1dUx9/F6vTp27NhqRQSAVcdlBgCQICYmJuRyueZ+/Y1vfENvvfWWGhsb9b3vfU8ffPBBzH3cbrfGx8dXMyYArCrKLAAkiKKiIg0NDc39+t5779WlS5f07W9/Wz09Pbr77rv1ne98x3KfwcFBFRcXr3ZUAFg1lFkASBBNTU1qaWmxzIqLi/XII4/ojTfe0AsvvKAf/ehHlt8/deqUmpqaVjMmAKwqyiwAJIg9e/bo9OnTc6/O7tu3T++++67OnTun06dP62c/+5nq6urmbj8+Pq4jR47onnvuMRUZAFYcZRYAEsTWrVu1fft2/eQnP5EkZWRk6Omnn9a2bdv0hS98QQ6HQ2+99dbc7d99912tWbNGv/M7v2MqMgCsOD40AQASyM9//nN997vf1alTp2S3f/brEXfccYf+9E//VH/4h3+4SukAYPVxNBcAJJD77rtP7e3t8vl8qqqqWvR2/f39+vKXv6yvfvWrq5gOAFYfr8wCAAAgYXHNLAAAABIWZRYAAAAJizILAACAhEWZBQAAQMKizAIAACBhUWYBAACQsCizAAAASFiUWQAAACQsyiwAAAAS1v8Pjv/yf0QC4hQAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# An essential feature of blueprints is that they can be modified\n", + "\n", + "bp_mod = bb.BluePrint()\n", + "bp_mod.setSR(100)\n", + "\n", + "bp_mod.insertSegment(0, ramp, (0, 0), name=\"before\", dur=1)\n", + "bp_mod.insertSegment(1, ramp, (1, 1), name=\"plateau\", dur=1)\n", + "bp_mod.insertSegment(2, ramp, (0, 0), name=\"after\", dur=1)\n", + "\n", + "plotter(bp_mod)\n", + "\n", + "# Functional arguments can be changed\n", + "\n", + "# They are looked up by segment name\n", + "bp_mod.changeArg(\n", + " \"before\", \"stop\", 1\n", + ") # the argument to change may either be the argument name or its position\n", + "bp_mod.changeArg(\"after\", 0, 1)\n", + "\n", + "plotter(bp_mod)\n", + "\n", + "# Durations can also be changed\n", + "bp_mod.changeDuration(\"plateau\", 2)\n", + "\n", + "plotter(bp_mod)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Special segments\n", + "([back to ToC](#Table-of-Contents))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.289978Z", + "iopub.status.busy": "2025-01-20T06:27:57.289625Z", + "iopub.status.idle": "2025-01-20T06:27:57.412263Z", + "shell.execute_reply": "2025-01-20T06:27:57.411777Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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ioqKdNddd+m+++4zXB1gn2PHjmnChAl69dVXdckll5guB7BFe3u75syZo1/+8pf6yU9+ovLycj388MOmywKG1X333ac333xTr7/+uulSTonOrE16e3tVW1urxYsXDxzz+/1avHixNm7caLAywH6tra2SpJycHMOVAPZZvny5li5dGvc6D3jNX//6V82dO1fXXnutJkyYoIqKCq1du9Z0WXEIszZpbm5WNBpVXl5e3PG8vDw1NjYaqgqwn2VZuueee3TxxRdr5syZpssBbPHss89q69atWr16telSAFvt379fv/rVr1RSUqKXX35Zd955p+6++249/fTTpksbEDRdAABvWb58uXbu3Kk33njDdCmALRoaGvStb31LGzZsUDgcNl0OYCvLsjR37lw98MADkqSKigrt3LlTa9as0c0332y4upPozNokNzdXgUBATU1NccebmpqUn59vqCrAXitWrNCLL76of/3rX5o0aZLpcgBb1NbW6ujRo5ozZ46CwaCCwaBeffVVPfLIIwoGg4pGo6ZLBIZNQUGBpk+fHnestLRU9fX1hipKRJi1SXp6uiorK1VTUzNwzLIs1dTUaOHChQYrA4ZfLBbTihUr9Je//EX//Oc/NXXqVNMlAba5/PLLtWPHDm3fvn3ga+7cubrhhhu0fft2BQIB0yUCw+biiy9O2GrxvffeU3FxsaGKEjHNwEbV1dW6+eabNXfuXM2fP18PP/ywOjo6dMstt5guDRhWy5cv1zPPPKMXXnhBmZmZA/PCs7OzlZGRYbg6YHhlZmYmzAcfPXq0xo0bxzxxeM69996rRYsW6YEHHtDXv/51bdq0SU888YSeeOIJ06UNYGsumz366KP6v//7PzU2Nqq8vFyPPPKIqqqqTJcFDCufz5f0+FNPPaVvfvObzhYDGHDppZeyNRc868UXX9TKlSu1b98+TZ06VdXV1Vq2bJnpsgYQZgEAAOBazJkFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFABc5fvy4JkyYoA8//PC05zU3N2vChAk6ePCgM4UBgCFcAQwAXKS6ulptbW1au3btGc/99re/rZaWFv3mN79xoDIAMIMwCwAu0dnZqYKCAr388stasGDBGc/ftWuXKisrdfjwYeXk5DhQIQA4j2kGAOASf/vb3xQKhQaCbEtLi2644QaNHz9eGRkZKikp0VNPPTVw/owZM1RYWKi//OUvpkoGANsFTRcAADg7r7/+uiorKwdu/+AHP9Du3bv197//Xbm5uXr//ffV1dUVd5/58+fr9ddf12233eZ0uQDgCMIsALhEXV2dCgsLB27X19eroqJCc+fOlSRNmTIl4T6FhYXatm2bUyUCgOOYZgAALtHV1aVwODxw+84779Szzz6r8vJyffe739Vbb72VcJ+MjAx1dnY6WSYAOIowCwAukZubq5aWloHbV155perq6nTvvffq8OHDuvzyy/Xtb3877j4nTpzQ+PHjnS4VABxDmAUAl6ioqNDu3bvjjo0fP14333yz1q1bp4cfflhPPPFE3Pd37typiooKJ8sEAEcRZgHAJZYsWaJdu3YNdGdXrVqlF154Qe+//7527dqlF198UaWlpQPnd3Z2qra2VldccYWpkgHAdoRZAHCJWbNmac6cOfrjH/8oSUpPT9fKlSs1e/ZsXXLJJQoEAnr22WcHzn/hhRc0efJkfe5znzNVMgDYjosmAICLvPTSS/rOd76jnTt3yu8/fT9iwYIFuvvuu/WNb3zDoeoAwHlszQUALrJ06VLt27dPhw4dUlFR0SnPa25u1tVXX63rr7/eweoAwHl0ZgEAAOBazJkFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAa/1/BKJP7P48p8IAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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IYzmSqDKykotX75cDz74oCTJsiwVFRXp61//uu6++26bqwMS5/Tp05oxY4ZefPFFXXHFFXaXAyREZ2enli5dql/+8pf68Y9/rLKyMj3wwAN2lwWMq7vvvluvvPKKXnrpJbtLuSA6swnS39+v2tparVmzZuiY2+3WmjVrtHPnThsrAxKvvb1dkpSTk2NzJUDirF+/XmvXrg37PQ84zZ///GctW7ZMN9xwg2bMmKHy8nJt3brV7rLCEGYTpLW1VcFgUHl5eWHH8/Ly1NzcbFNVQOJZlqU777xTl19+uRYtWmR3OUBCPPnkk6qrq9PmzZvtLgVIqPr6ev3qV7/SvHnz9Le//U133HGHvvGNb+jxxx+3u7QhXrsLAOAs69ev14EDB/Tyyy/bXQqQEE1NTfrmN7+pHTt2yO/3210OkFCWZWnZsmW69957JUnl5eU6cOCAtmzZoltuucXm6s6jM5sgubm58ng8amlpCTve0tKi/Px8m6oCEmvDhg16/vnn9c9//lOzZs2yuxwgIWpra3Xq1CktXbpUXq9XXq9XL774on7xi1/I6/UqGAzaXSIwbgoKCrRgwYKwYyUlJWpsbLSpomiE2QRJT09XRUWFampqho5ZlqWamhqtWrXKxsqA8RcKhbRhwwY988wz+sc//qG5c+faXRKQMFdffbX279+vffv2Db0tW7ZMN910k/bt2yePx2N3icC4ufzyy6NGLb755psqLi62qaJoLDNIoOrqat1yyy1atmyZVqxYoQceeEBdXV269dZb7S4NGFfr16/XE088oeeee06ZmZlD68Kzs7OVkZFhc3XA+MrMzIxaDz558mRNmzaNdeJwnLvuukurV6/Wvffeqy984QvatWuXHnnkET3yyCN2lzaE0VwJ9uCDD+r//u//1NzcrLKyMv3iF79QZWWl3WUB48rlcsU8/thjj+krX/lKcosBbHDllVcymguO9fzzz2vjxo06evSo5s6dq+rqaq1bt87usoYQZgEAAGAs1swCAADAWIRZAAAAGIswCwAAAGMRZgEAAGAswiwAAACMRZgFAACAsQizAAAAMBZhFgAAAMYizAKAQc6cOaMZM2bonXfeGfa81tZWzZgxQ8ePH09OYQBgE+4ABgAGqa6uVkdHh7Zu3Triud/61rfU1tam3/zmN0moDADsQZgFAEN0d3eroKBAf/vb37Ry5coRzz948KAqKip08uRJ5eTkJKFCAEg+lhkAgCH+8pe/yOfzDQXZtrY23XTTTZo+fboyMjI0b948PfbYY0PnL1y4UIWFhXrmmWfsKhkAEs5rdwEAgNF56aWXVFFRMfT+97//fR06dEh//etflZubq7feeks9PT1hj1mxYoVeeukl3XbbbckuFwCSgjALAIZoaGhQYWHh0PuNjY0qLy/XsmXLJElz5syJekxhYaH27t2brBIBIOlYZgAAhujp6ZHf7x96/4477tCTTz6psrIyfec739Grr74a9ZiMjAx1d3cns0wASCrCLAAYIjc3V21tbUPvX3vttWpoaNBdd92lkydP6uqrr9a3vvWtsMecPXtW06dPT3apAJA0hFkAMER5ebkOHToUdmz69Om65ZZbtG3bNj3wwAN65JFHwj5+4MABlZeXJ7NMAEgqwiwAGKKqqkoHDx4c6s5u2rRJzz33nN566y0dPHhQzz//vEpKSobO7+7uVm1tra655hq7SgaAhCPMAoAhFi9erKVLl+rpp5+WJKWnp2vjxo1asmSJrrjiCnk8Hj355JND5z/33HOaPXu2Pvaxj9lVMgAkHDdNAACDvPDCC/r2t7+tAwcOyO0evh+xcuVKfeMb39CXvvSlJFUHAMnHaC4AMMjatWt19OhRnThxQkVFRRc8r7W1VZ///Of1xS9+MYnVAUDy0ZkFAACAsVgzCwAAAGMRZgEAAGAswiwAAACMRZgFAACAsQizAAAAMBZhFgAAAMYizAIAAMBYhFkAAAAYizALAAAAY/1/pStQ6JZ8bTEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The 'waituntil' segment fills up a part of the blueprint with zeros\n", + "\n", + "# Example: a square pulse, then waiting until 5 s exactly and then a new sine\n", + "\n", + "bp_wait = bb.BluePrint()\n", + "bp_wait.setSR(100)\n", + "\n", + "bp_wait.insertSegment(0, ramp, (0, 0), dur=1)\n", + "bp_wait.insertSegment(1, ramp, (1, 1), name=\"plateau\", dur=1)\n", + "# function must be sthe string 'waituntil', the argument is the ABSOLUTE time to wait until\n", + "bp_wait.insertSegment(2, \"waituntil\", (5,))\n", + "bp_wait.insertSegment(3, sine, (1, 0.1, 0, -np.pi / 2), dur=1)\n", + "plotter(bp_wait)\n", + "\n", + "# If we make the square pulse longer, the sine still occurs at 5 s\n", + "bp_wait.changeDuration(\"plateau\", 1.5)\n", + "plotter(bp_wait)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Elements\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "Elements are containers containing blueprints. A valid element consists of blueprints that all have the same number of points and the same overall duration." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.413958Z", + "iopub.status.busy": "2025-01-20T06:27:57.413796Z", + "iopub.status.idle": "2025-01-20T06:27:57.533643Z", + "shell.execute_reply": "2025-01-20T06:27:57.533093Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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TZ0NUzTryUNVhCkK+VNnw+jzNxz4VGE77D6fKhBRaAiYNHOEnKDT+fY0M2mmILjopHnTpk246667kJ6ejocffhg333wzvvzyy5GITdUcfT2o27fNb4D8iMho5HHSkrCgjzAid8osmGLjvZa7nf2o278DfV0d53glEY1WvsPNabRajpmvAP+eZo6eQYG99957+N3vfjes1w4raW5sbMRzzz2HoqIiXHHFFaiursZrr72GxsZGrFu3zmu6Qho6e2836vbvgKvf4bXcGB2DvKllfAo7jOgNEcidPBORPmU2ksuJ+gM70ctZrYhCitvl9PqZ9czK8G1n3xFMiM545JFHcPfddw/rtUP+b7722mvx73//GykpKbjjjjtwzz33YPz48cN6c/Jn7+lC/YEdcDu9T7zGmLiBYckMEYIio2DRnX6g88TB3V5Tv8puN05U7EL2pOmISUwRGCERXSjfB9BYmqEMjp5BF+rw4cPDfu2Qk2aDwYC//OUvuP7666HjyWBE9XV14ETFbkg+PRWRcQnImTQDugDTllN40J4eOtB34hpZknDy4G5kTZiK2BROCUs02vmWBfgOMUnB4fewpSxDcrt500IjashJ8z//+c9gxKF6fdb2gYTZ5+44Mj6RM8aphFanQ3ZxKRoO70P3KYtnuSxJOHloL7ImTEFcaobACIloMP5jNDNpU0KgMhjJ7WL7q1xnZye2b9+OlpYWSGdNLAYAd9xxx5D3N+xiK7vdjtdffx2fffZZwGB279493F1ftDVr1uDnP/85mpubMXXqVLz++usoKysTFs9gejpODcwU59NDEZ2YjOzi6fynVxGNVousCVPRdLQCVkvj1ytkGQ2H90OSJCSYs8QFSETn5TsTnY41zYoIdJ10u5ycaVXF3n//fSxatAg9PT2Ii4uDRqPxrNNoNMomzYsXL8bHH3+MW265BWVlZV7BiPTuu+9i+fLlWLt2LWbPno3Vq1ejvLwcVVVVSEtLEx2en572Vr+plYGB2eOyiqfxqWsV0mi1yCiaDI1Gi87mk1+vkGU0VR2ALElIzMgRFyARnZN/TTOTZiVotTpotN5jG3AEDXVbsWIF7rnnHjz33HOIihqZEceG/d/8wQcf4MMPP8TcuXNHJJCR8sorr2DJkiWeJyPXrl2L9evX480338Sjjz4qODpvXa3NaKza75cwx6aYkTVhqt8J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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=0.1e-6)\n", + "bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name=\"top\", dur=0.1e-6)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=0.1e-6)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 25e-3, 0, 0), dur=0.3e-6)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "# Now we create an element and add the blueprints to channel 1 and 3, respectively\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "\n", + "# We can check the validity of the element\n", + "elem1.validateDurations() # raises an ElementDurationError if something is wrong. If all is OK, does nothing.\n", + "\n", + "# And we can plot the element\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.535463Z", + "iopub.status.busy": "2025-01-20T06:27:57.535133Z", + "iopub.status.idle": "2025-01-20T06:27:57.538550Z", + "shell.execute_reply": "2025-01-20T06:27:57.538024Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Designated channels: [1, 3]\n", + "Total duration: 6e-07 s\n", + "Sample rate: 1000000000.0 (Sa/S)\n" + ] + } + ], + "source": [ + "# An element has several features\n", + "print(f\"Designated channels: {elem1.channels}\")\n", + "print(f\"Total duration: {elem1.duration} s\")\n", + "print(f\"Sample rate: {elem1.SR} (Sa/S)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.540232Z", + "iopub.status.busy": "2025-01-20T06:27:57.539922Z", + "iopub.status.idle": "2025-01-20T06:27:57.702398Z", + "shell.execute_reply": "2025-01-20T06:27:57.701814Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# We can modify the blueprints of an element through the element object\n", + "\n", + "# Change the sine freq\n", + "elem1.changeArg(\n", + " 3, \"sine\", \"freq\", 6.67e6\n", + ") # Call signature: channel, segment name, argument, new_value\n", + "\n", + "# make the second plateaus last longer\n", + "elem1.changeDuration(\n", + " 1, \"top2\", 0.2e-6\n", + ") # In this blueprint, the second top is called top2\n", + "elem1.changeDuration(3, \"top\", 0.2e-6)\n", + "\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sequences\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "Finally, we have reached the top level of the module: sequences. Unsurprisingly, sequences are containers containing elements. All elements in a sequence must specify the same channels." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.704396Z", + "iopub.status.busy": "2025-01-20T06:27:57.703997Z", + "iopub.status.idle": "2025-01-20T06:27:57.710648Z", + "shell.execute_reply": "2025-01-20T06:27:57.710080Z" + } + }, + "outputs": [], + "source": [ + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "#\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(3, bp_boxes)\n", + "elem2.addBluePrint(1, bp_sineandboxes)\n", + "\n", + "# Fill up the sequence\n", + "seq1.addElement(1, elem1) # Call signature: seq. pos., element\n", + "seq1.addElement(2, elem2)\n", + "seq1.addElement(3, elem1)\n", + "\n", + "# set its sample rate\n", + "seq1.setSR(elem1.SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.712429Z", + "iopub.status.busy": "2025-01-20T06:27:57.712100Z", + "iopub.status.idle": "2025-01-20T06:27:57.715124Z", + "shell.execute_reply": "2025-01-20T06:27:57.714659Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on method changeArg in module broadbean.element:\n", + "\n", + "changeArg(channel: 'str | int', name: 'str', arg: 'str | int', value: 'int | float', replaceeverywhere: 'bool' = False) -> 'None' method of broadbean.element.Element instance\n", + " Change the argument of a function of the blueprint on the specified\n", + " channel.\n", + " \n", + " Args:\n", + " channel: The channel where the blueprint sits.\n", + " name: The name of the segment in which to change an argument\n", + " arg: Either the position (int) or name (str) of\n", + " the argument to change\n", + " value: The new value of the argument\n", + " replaceeverywhere: If True, the same argument is overwritten\n", + " in ALL segments where the name matches. E.g. 'gaussian1' will\n", + " match 'gaussian', 'gaussian2', etc. If False, only the segment\n", + " with exact name match gets a replacement.\n", + " \n", + " Raises:\n", + " ValueError: If the specified channel has no blueprint.\n", + " ValueError: If the argument can not be matched (either the argument\n", + " name does not match or the argument number is wrong).\n", + "\n" + ] + } + ], + "source": [ + "help(seq1.element(1).changeArg)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.716796Z", + "iopub.status.busy": "2025-01-20T06:27:57.716455Z", + "iopub.status.idle": "2025-01-20T06:27:57.935946Z", + "shell.execute_reply": "2025-01-20T06:27:57.935420Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The sequence can be validated\n", + "seq1.checkConsistency() # returns True if all is well, raises errors if not\n", + "\n", + "# And the sequence can (if valid) be plotted\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tektronix AWG 5014 output\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The sequence object can output a tuple matching the call signature of the QCoDeS Tektronix AWG 5014 driver.\n", + "\n", + "For the translation from voltage to AWG unsigned integer format to be correct, the voltage ranges and offsets of the AWG channels must be specified (NB: This will **NOT** work if the channels on the AWG are in high/low mode).\n", + "\n", + "Furthermore, the AWG sequencer options should be specified for each sequence element." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:57.937813Z", + "iopub.status.busy": "2025-01-20T06:27:57.937448Z", + "iopub.status.idle": "2025-01-20T06:27:58.183907Z", + "shell.execute_reply": "2025-01-20T06:27:58.183381Z" + }, + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/png": "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l3uMVG8A1h3WncwxUt1hvcmPesOW4o9UVFTAYrEongTt6elBdXW1RqXKnPXr1+P555/HK6+8ghkzZsSWV1dXIxwOw+fzyT4/+XdXV1cn3C4n39OjPXv2oLe3F+eccw6sViusVit27NiBRx99FFarFV6vNy9/NwDU1NTgjDPOkC1buHAhWltbAXxc9lPt69XV1ejt7ZW9H41GMTg4KPvtrDc+2efzYf9h3WHdyQSj1R3WG3XqTbYxKf6I3W7H0qVLsX379tgyURSxfft2nH/++RqWLD2SJGH9+vX4zW9+g5dffhkzZ86Uvb906VLYbDbZ7z58+DBaW1tjv/v888/H+++/L9th//znP8Ptdisqgl5cdNFFeP/997F3797Yv2XLluHaa6+N/T8ffzcAXHjhhYohkD744AM0NjYCAGbOnInq6mrZb/f7/Xj77bdlv93n82HPnj2xz7z88ssQRRErVqyILWO9yb/9h3WHdScdRq07rDfq1JusU+2Rvhzwi1/8QnI4HNIzzzwjHThwQPrqV78qlZSUyJ4EzTW33nqr5PF4pFdffVXq6uqK/RsbG4t95mtf+5rU0NAgvfzyy9Lu3bul888/Xzr//PNj758cJubiiy+W9u7dK/3xj3+UKisrdT9MTLzJTwJLUv7+7l27dklWq1XatGmTdOTIEelnP/uZ5HK5pP/6r/+KfebBBx+USkpKpOeee0567733pM997nMJh8dpbm6W3n77bemNN96Q5s6dO+WwUqw3+bP/JMK6w7ozXaw7H2O9yU69ySYmxXEee+wxqaGhQbLb7dK5554rvfXWW1oXKS0AEv57+umnY58JBoPS17/+dam0tFRyuVzS3//930tdXV2y9Zw4cUK69NJLJafTKVVUVEi33367FIlEVP416Yk/QOXz7/79738vLVq0SHI4HNKCBQukJ598Uva+KIrSd7/7Xcnr9UoOh0O66KKLpMOHD8s+MzAwIH3pS1+SioqKJLfbLd1www3SyMhIwnisN/m1/8Rj3fkY686pse58jPXmY5muN9likiRJUq9dmoiIiIhIf9inmIiIiIgMj0kxERERERkek2IiIiIiMjwmxURERERkeEyKiYiIiMjwmBQTERERkeExKSYiIiIiw2NSTERERESGx6SYsmpgYABVVVU4ceJE2uv64x//iLPPPhuiKKZfMCKdY90hSh7rDaWDSTFl1aZNm/C5z30OTU1Naa/rkksugc1mw89+9rP0C0akc6w7RMljvaF0MCmmrBkbG8N//Md/4KabbsrYOteuXYtHH300Y+sj0iPWHaLksd5QupgUU9Zs27YNDocD5513HgDg1Vdfhclkwvbt27Fs2TK4XC5ccMEFOHz4cOw77777Lj796U+juLgYbrcbS5cuxe7du2Pvf/azn8Xu3bvx4Ycfqv57iNTCukOUPNYbSheTYsqa119/HUuXLlUsv/vuu/Hwww9j9+7dsFqtuPHGG2PvXXvttZgxYwbeeecd7NmzB3fddRdsNlvs/YaGBni9Xrz++uuq/AYiLbDuECWP9YbSZdW6AJS/WlpaUFtbq1i+adMmrFy5EgBw11134fLLL8f4+DgKCgrQ2tqKO+64AwsWLAAAzJ07V/H92tpatLS0ZLfwRBpi3SFKHusNpYstxZQ1wWAQBQUFiuVnnXVW7P81NTUAgN7eXgDAhg0b8JWvfAWrV6/Ggw8+mPCWldPpxNjYWJZKTaQ91h2i5LHeULqYFFPWVFRUYGhoSLF88q0pk8kEALEhb/75n/8Z+/fvx+WXX46XX34ZZ5xxBn7zm9/Ivj84OIjKysoslpxIW6w7RMljvaF0MSmmrGlubsaBAweS/t68efPwrW99Cy+++CK+8IUv4Omnn469Nz4+jg8//BDNzc2ZLCqRrrDuECWP9YbSxaSYsmbNmjXYv39/wiv3RILBINavX49XX30VLS0t+Mtf/oJ33nkHCxcujH3mrbfegsPhwPnnn5+tYhNpjnWHKHmsN5QuJsWUNYsXL8Y555yD//t//++0Pm+xWDAwMIDrrrsO8+bNwz/8wz/g0ksvxf333x/7zH//93/j2muvhcvlylaxiTTHukOUPNYbSpdJkiRJ60JQ/nrhhRdwxx13YN++fTCb07sG6+/vx/z587F7927MnDkzQyUk0ifWHaLksd5QOjgkG2XV5ZdfjiNHjqCjowP19fVprevEiRP48Y9/zIMTGQLrDlHyWG8oHWwpJiIiIiLDY59iIiIiIjI8JsVEREREZHhMiomIiIjI8JgUExEREZHhMSkmIiIiIsNjUkxEREREhsekmIiIiIgMj0kxERERERkek2IiIiIiMrz/H3t4rLmZFrtUAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", + "seq1.setChannelOffset(1, 0)\n", + "seq1.setChannelAmplitude(3, 10e-3)\n", + "seq1.setChannelOffset(3, 0)\n", + "\n", + "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", + "seq1.setSequencingTriggerWait(1, 0)\n", + "seq1.setSequencingNumberOfRepetitions(1, 2)\n", + "seq1.setSequencingEventJumpTarget(1, 0)\n", + "seq1.setSequencingGoto(1, 2)\n", + "#\n", + "seq1.setSequencingTriggerWait(2, 0)\n", + "seq1.setSequencingNumberOfRepetitions(2, 2)\n", + "seq1.setSequencingEventJumpTarget(2, 0)\n", + "seq1.setSequencingGoto(1, 3)\n", + "#\n", + "seq1.setSequencingTriggerWait(3, 0)\n", + "seq1.setSequencingNumberOfRepetitions(3, 2)\n", + "seq1.setSequencingEventJumpTarget(3, 0)\n", + "seq1.setSequencingGoto(3, 1)\n", + "\n", + "# then we may finally get the \"package\" to give the QCoDeS driver for upload\n", + "package = seq1.outputForAWGFile()\n", + "\n", + "# Note that the sequencing information is included in the plot in a way mimicking the\n", + "# way the display of the Tektronix AWG 5014\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:58.185755Z", + "iopub.status.busy": "2025-01-20T06:27:58.185410Z", + "iopub.status.idle": "2025-01-20T06:27:58.189721Z", + "shell.execute_reply": "2025-01-20T06:27:58.189172Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 3]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The package is a SLICEABLE object\n", + "# By slicing and indexing it, one may retrieve different parts of the sequence\n", + "\n", + "chan1_awg_input = package[0] # returns a tuple yielding an awg file with channel 1\n", + "chan3_awg_input = package[1] # returns a tuple yielding an awg file with channel 3\n", + "\n", + "both_chans_awg_input = package[\n", + " :\n", + "] # returns a tuple yielding an awg file with both channels\n", + "\n", + "# This may be useful to make one big sequence for one experiment and then uploading part of it to one awg\n", + "# and part of it to another (since physical awg's usually don't have enough channels for a big experiment)\n", + "\n", + "# To see how the channels are counted, look up the channels\n", + "package.channels" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:58.191478Z", + "iopub.status.busy": "2025-01-20T06:27:58.191153Z", + "iopub.status.idle": "2025-01-20T06:27:58.193691Z", + "shell.execute_reply": "2025-01-20T06:27:58.193255Z" + } + }, + "outputs": [], + "source": [ + "## Example of uploading the sequence (requires having qcodes installed, see https://github.com/QCoDeS/Qcodes)\n", + "\n", + "# from qcodes.instrument_drivers.tektronix.AWG5014 import Tektronix_AWG5014\n", + "# awg = Tektronix_AWG5014('AWG1', 'TCPIP0::172.20.3.57::inst0::INSTR', timeout=40)\n", + "# awg.make_send_and_load_awg_file(*package[:])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delays and filter compensation\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "In a real experimental setting, the signal transmission line may distort and/or delay the pulse sequence. The Sequence object can perform some compensation for this when making the `.awg` file." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:58.195394Z", + "iopub.status.busy": "2025-01-20T06:27:58.195071Z", + "iopub.status.idle": "2025-01-20T06:27:58.201523Z", + "shell.execute_reply": "2025-01-20T06:27:58.200989Z" + } + }, + "outputs": [], + "source": [ + "# To delay channel 1 with respect to the other channels, set its delay\n", + "seq1.setChannelDelay(1, 0)\n", + "seq1.setChannelDelay(3, 123e-9)\n", + "\n", + "# To apply for a high pass filter with a cut-off frequency of 1 MHz on channel 3, we can do\n", + "seq1.setChannelFilterCompensation(3, \"HP\", order=1, f_cut=1e6)\n", + "# or, equivalently,\n", + "seq1.setChannelFilterCompensation(3, \"HP\", order=1, tau=1e-6)\n", + "\n", + "# Note that setting the filter compensation may invalidate the sequence in the sense that the specified voltage ranges\n", + "# on the AWG may have become too small. The function outputForAWGFile will warn you if this is the case.\n", + "\n", + "newpackage = seq1.outputForAWGFile()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:58.203407Z", + "iopub.status.busy": "2025-01-20T06:27:58.203096Z", + "iopub.status.idle": "2025-01-20T06:27:58.453210Z", + "shell.execute_reply": "2025-01-20T06:27:58.452672Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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X3eMXm9kmXUnF+uO2euNLBu/7uRbvVGLIMuyrHXrNBERERFRLmGfYiIiIiLKewyKiYiIiCjvMSgmIiIiorzHoJiIiIiI8h6DYiIiIiLKewyKiYiIiCjvMSgmIiIiorzHoJiIiIiI8h6DYlJVT08PKisrcfTo0YzTevXVV3HmmWdCkqTMC0aU41h3iKaO9YYywaCYVPXwww/j85//PGbMmJFxWpdccgnsdjt+9rOfZV4wohzHukM0daw3lAkGxaSaoaEh/Md//AduueWWrKV544034sknn8xaekS5iHWHaOpYbyhTDIpJNZs2bYLT6cQ555wDAHjrrbcgCAI2b96MZcuWwe1247zzzsO+ffvin/noo4/wt3/7tygqKoLX68XSpUuxffv2+Puf+9znsH37dhw6dEjz70OkFdYdoqljvaFMMSgm1bzzzjtYunRp0vZ7770Xjz/+OLZv3w6bzYabb745/t51112HadOm4YMPPsCOHTtwzz33wG63x99vaGhAVVUV3nnnHU2+A5EeWHeIpo71hjJl07sAZF5NTU2ora1N2v7www9j5cqVAIB77rkHl19+OYaHh1FQUIDm5mbcddddmD9/PgBgzpw5SZ+vra1FU1OTuoUn0hHrDtHUsd5QpthSTKoJhUIoKChI2n7GGWfE/7+mpgYA0NnZCQBYt24dvvKVr2DVqlV49NFHx3xk5XK5MDQ0pFKpifTHukM0daw3lCkGxaSa8vJy9Pb2Jm0f/WhKEAQAiE9588///M/49NNPcfnll+ONN97A6aefjt/85jeKz/v9flRUVKhYciJ9se4QTR3rDWWKQTGpprGxEbt3757y5+bOnYtvfetbeO2113D11Vfj+eefj783PDyMQ4cOobGxMZtFJcoprDtEU8d6Q5liUEyqWb16NT799NMx79zHEgqFsHbtWrz11ltoamrCn//8Z3zwwQdYsGBBfJ/3338fTqcT5557rlrFJtId6w7R1LHeUKYYFJNqFi9ejLPOOgv/9//+35T2t1qt6OnpwfXXX4+5c+fiH/7hH3DppZfioYceiu/z3//937juuuvgdrvVKjaR7lh3iKaO9YYyJciyLOtdCDKvV155BXfddRd27doFiyWze7Du7m7MmzcP27dvx8yZM7NUQqLcxLpDNHWsN5QJTslGqrr88stx4MABtLS0oL6+PqO0jh49ih/96Ec8OVFeYN0hmjrWG8oEW4qJiIiIKO+xTzERERER5T0GxURERESU9xgUExEREVHeY1BMRERERHmPQTERERER5T0GxURERESU9xgUExEREVHeY1BMRERERHmPQTERERER5b3/H6rSEFhJzt1XAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For sanity checking, it may be helpful to see how the compensated waveforms look.\n", + "# The plotter function can display the delays and filter compensations\n", + "\n", + "plotter(seq1, apply_filters=True, apply_delays=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sequences varying parameters\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The module contains a few wrapper functions to easily generate sequences with some parameter(s) varying throughout the sequence. First two examples where a Base element is provided and varied, then an example where an existing Sequence is repeated." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:58.455037Z", + "iopub.status.busy": "2025-01-20T06:27:58.454681Z", + "iopub.status.idle": "2025-01-20T06:27:58.705249Z", + "shell.execute_reply": "2025-01-20T06:27:58.704799Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1: vary the duration of a square pulse\n", + "\n", + "# First, we make a basic element, in this example containing just a single blueprint\n", + "basebp = bb.BluePrint()\n", + "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", + "basebp.setSR(100)\n", + "\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, basebp)\n", + "\n", + "plotter(baseelem)\n", + "\n", + "# Now we make a 5-step sequence varying the duration of the high level\n", + "# The inputs are lists, since several things can be varied (see Example 2)\n", + "channels = [1]\n", + "names = [\"varyme\"]\n", + "args = [\"duration\"]\n", + "iters = [[1, 1.5, 2, 2.5, 3]]\n", + "\n", + "seq1 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:58.707212Z", + "iopub.status.busy": "2025-01-20T06:27:58.706741Z", + "iopub.status.idle": "2025-01-20T06:27:58.957946Z", + "shell.execute_reply": "2025-01-20T06:27:58.957423Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 2: Vary the duration AND the high level\n", + "\n", + "# It is straighforward to vary several things throughout the sequence\n", + "\n", + "# We make the same base element as in Example 1\n", + "basebp = bb.BluePrint()\n", + "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", + "basebp.setSR(100)\n", + "\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, basebp)\n", + "\n", + "# Now we make a 5-step sequence varying the duration AND the high level\n", + "# We thus vary 3 things, a duration, a ramp start, and a ramp stop\n", + "channels = [1, 1, 1]\n", + "names = [\"varyme\", \"varyme\", \"varyme\"]\n", + "args = [\"duration\", \"start\", \"stop\"]\n", + "iters = [[1, 1.5, 2, 2.5, 3], [1, 0.8, 0.7, 0.6, 0.5], [1, 0.8, 0.7, 0.6, 0.5]]\n", + "\n", + "seq2 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", + "plotter(seq2)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:27:58.959839Z", + "iopub.status.busy": "2025-01-20T06:27:58.959473Z", + "iopub.status.idle": "2025-01-20T06:27:59.352135Z", + "shell.execute_reply": "2025-01-20T06:27:59.351601Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:190: UserWarning: Deprecation warning. This function is only compatible with AWG5014 output and will be removed. Please use the specific setSequencingXXX methods.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "image/png": "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/ywMRiN/odEwJsQ0UTxmJk/3UorNmzfjhhtuwLJly7B8+XJs374dkUgEN954IwBgw4YNmD59OrZu3QoA+OpXv4qLLroI27Ztw+WXX47f/OY32LdvHx5++GE9PwYRERERFTndE+Nrr70Wvb29uPfee9HV1YVFixbhhRdeGL7Brq2tTTEjef755+OJJ57A3XffjW9961uYPXs2nn766Zz1MCYiIiKi8qB7YgwAmzZtGrV04uWXX9aMrV+/HuvXr89xVERERERUTliEQkRERESEApkxzqehhf640Ed2iKKIcCQM4PTPdCKr2lB54jGT2VR+LkPns87OzpzERkQ0GaIoorv3dCexkydPTui8NnQ+k/Lc2rPsEuNQKAQA7GVMRCVn+fLleodARJRV3d3dmDFjRt72J8hDU6hlQpIkdHR0wO12T6gd0tDCIO3t7fB4PDmMkEoFjxmaqMkeM6lUCnv27MH8+fNhMo1/viMUCmHevHk4cOAA3G73ZEKmMsNjhiZqsseMJEno7u7G4sWLJ3Rem6qyS4wnKxgMwuv1IhAIMMmhceExQxOV72OGxyhNFI8ZmqhiO2Z48x0REREREZgYExEREREBYGI8blarFffddx+sVqveoVCR4DFDE5XvY4bHKE0UjxmaqGI7ZlhjTEREREQEzhgTEREREQFgYkxEREREBICJMRERERERACbGREREREQAmBiP20MPPYSWlhbYbDasWLECe/fu1TskKlC7d+/GFVdcgWnTpkEQBDz99NN6h0QFbuvWrTjvvPPgdrtRW1uLK6+8EocPH875fnleo/HasmULBEFQ/DnnnHP0DosKyFi/+2RZxr333ouGhgbY7XasWbMGR44c0SfYM2BiPA5PPvkkNm/ejPvuuw9vvvkmFi5ciHXr1qGnp0fv0KgARSIRLFy4EA899JDeoVCR+Otf/4rbbrsNr7/+Onbu3IlUKoW1a9ciEonkbJ88r9FEnXvuuejs7Bz+8+qrr+odEhWQsX73ff/738e//du/YceOHXjjjTfgdDqxbt06xOPxPEd6ZmzXNg4rVqzAeeedhwcffBDA6fW7m5qa8OUvfxl33nmnztFRIRMEAb///e9x5ZVX6h0KFZHe3l7U1tbir3/9Ky688MKc7IPnNZqILVu24Omnn0Zra6veoVARUP/uk2UZ06ZNw9e//nV84xvfAAAEAgHU1dXhsccewxe+8AUdo1XijPEYkskk9u/fjzVr1gyPGQwGrFmzBnv27NExMiIqVYFAAABQWVmZk+3zvEaTceTIEUybNg2zZs3C9ddfj7a2Nr1DoiJx7NgxdHV1Kc45Xq8XK1asKLhzDhPjMfT19UEURdTV1SnG6+rq0NXVpVNURFSqJEnC7bffjgsuuADz58/PyT54XqOJWrFiBR577DG88MIL+NnPfoZjx47h05/+NEKhkN6hUREYOq8UwznHpHcARET0sdtuuw3vvfce6zepoFx66aXD///JT34SK1asQHNzM5566incfPPNOkZGlF2cMR5DdXU1jEYjuru7FePd3d2or6/XKSoiKkWbNm3Cs88+i5deegmNjY052w/PazRVFRUVOPvss/Hhhx/qHQoVgaHzSjGcc5gYj8FisWDp0qXYtWvX8JgkSdi1axdWrVqlY2REVCpkWcamTZvw+9//Hn/5y18wc+bMnO6P5zWaqnA4jI8++ggNDQ16h0JFYObMmaivr1ecc4LBIN54442CO+ewlGIcNm/ejBtuuAHLli3D8uXLsX37dkQiEdx44416h0YFKBwOK2ZRjh07htbWVlRWVmLGjBk6RkaF6rbbbsMTTzyBZ555Bm63e7jmzuv1wm6352SfPK/RRHzjG9/AFVdcgebmZnR0dOC+++6D0WjEddddp3doVCDG+t13++2343vf+x5mz56NmTNn4p577sG0adMKr2uTTOPyk5/8RJ4xY4ZssVjk5cuXy6+//rreIVGBeumll2QAmj833HCD3qFRgcp0vACQH3300Zzul+c1Gq9rr71WbmhokC0Wizx9+nT52muvlT/88EO9w6ICMtbvPkmS5HvuuUeuq6uTrVarfMkll8iHDx/WN+gM2MeYiIiIiAisMSYiIiIiAsDEmIiIiIgIABNjIiIiIiIATIyJiIiIiAAwMSYiIiIiAsDEmIiIiIgIABNjIiIiIiIATIyJiIiIiAAwMSYat/7+ftTW1uL48eM52f6dd96JL3/5yznZNhFRJjyvESlx5Tuicdq8eTNCoRB+8Ytf5GT7fX19mDVrFlpbWzFr1qyc7IOIaCSe14iUmBgTjUM0GkVDQwNefPFFrFy5Mmf7Wb9+PVpaWvCDH/wgZ/sgIgJ4XiPKhKUUROPw3HPPwWq1Dv/yePnllyEIAl588UUsXrwYdrsdq1evRk9PD55//nnMnTsXHo8HX/ziFxGNRoe389vf/hYLFiyA3W5HVVUV1qxZg0gkMvz8FVdcgd/85jd5/3xEVH54XiPSMukdAFExeOWVV7B06VLN+JYtW/Dggw/C4XDgmmuuwTXXXAOr1YonnngC4XAYV111FX7yk5/gm9/8Jjo7O3Hdddfh+9//Pq666iqEQiG88sorGHnRZvny5Th58iSOHz+OlpaWPH5CIio3PK8RaTExJhqHEydOYNq0aZrx733ve7jgggsAADfffDPuuusufPTRR8O1dFdffTVeeuml4V8g6XQan//859Hc3AwAWLBggWJ7Q/s4ceIEf4EQUU7xvEakxVIKonGIxWKw2Wya8U9+8pPD/19XVweHw6G4waSurg49PT0AgIULF+KSSy7BggULsH79evziF7/A4OCgYnt2ux0AFJcpiYhygec1Ii0mxkTjUF1drTnZA4DZbB7+f0EQFI+HxiRJAgAYjUbs3LkTzz//PObNm4ef/OQnmDNnDo4dOzb8+oGBAQBATU1NLj4GEdEwnteItJgYE43D4sWLceDAgSlvRxAEXHDBBfjOd76Dt956CxaLBb///e+Hn3/vvfdgNptx7rnnTnlfRERnwvMakRYTY6JxWLduHd5///2Msyvj9cYbb+D+++/Hvn370NbWht/97nfo7e3F3Llzh1/zyiuv4NOf/vTwpUciolzheY1Ii4kx0TgsWLAAS5YswVNPPTXpbXg8HuzevRuXXXYZzj77bNx9993Ytm0bLr300uHX/OY3v8HGjRuzETIR0RnxvEakxQU+iMbpj3/8I+644w689957MBiy/53y+eefx9e//nW88847MJnYMIaIco/nNSIlHqVE43T55ZfjyJEjOHXqFJqamrK+/UgkgkcffZS/PIgob3heI1LijDEREREREVhjTEREREQEgIkxEREREREAJsZERERERACYGBMRERERAWBiTEREREQEgIkxEREREREAJsZERERERACYGBMRERERAWBiTEREREQEAPj/tL1yA0I5hP8AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsYAAAEwCAYAAAC0S7csAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAX25JREFUeJztnXmUXMV977+3by+zaGYkIWlmtEuWALFYSAIE8gLGCgJjHJJjzEnwwTEGLwc9x1Ge88ALOLFj+dhhSWJieQlwbMcxjuMlz2B4WEbIxMJCgILNDhJakEb77DO93Fvvj9E001XV09td6nZ/P+fMQXO7+/ZvqPur+tVvK0sIIUAIIYQQQkiDEwtbAEIIIYQQQkyAhjEhhBBCCCGgYUwIIYQQQggAGsaEEEIIIYQAoGFMCCGEEEIIABrGhBBCCCGEAKBhTAghhBBCCAAaxoQQQgghhACgYUwIIYQQQggAGsaEEEIIIYQAoGEcOTZv3ozrr78ep556KlpaWrB48WLccMMNOHjwoPb9v/3tb/H2t78dLS0t6Orqwic/+UkMDg4GLHXjcPz4cQwPD2tfO3jwIG6++Wa8613vQltbGyzLwpYtW4IVsEGh3pjNZHqzdetWvO9978O8efPQ1NSErq4uXHbZZfjv//7vgKVsTKg7ZjOZ7tx3332wLEv709PTE7Ck0cESQoiwhWhEfvzjHxc8mC0tLfiLv/gLxGKT71XOPfdcHD9+HFdffTWWLl2KXbt24etf/zpaWlqwc+dOdHV15d+7c+dOXHjhhVi2bBk++tGPYv/+/fiHf/gHvOtd78Ivf/lL3/62Rubiiy+Gbdv4xS9+gebm5oLXtmzZgne9611YunQpZsyYgW3btuHRRx/FxRdfHI6wEYR6U59Mpjff+c538Itf/ALnnXceurq6cOLECXz/+9/H73//ezzwwAO47LLLQpI6WlB36pPJdOe+++7Dhz/8Yfzd3/0dFi1aVPDa+9//fjQ1NQUpanQQJBRWr14tABT8PPbYYyU/99hjjwnHcZRrAMRnP/vZguuXX3656O7uFn19fflr3/72twUA8fDDD3vzh5ACtm7dKlpbW8Uf/dEfiZGRkYLX+vv7xbFjx4QQQvzHf/yHACAeffTREKSMLtSb+mQyvdExNDQkOjs7xbp16wKQrj6g7tQnk+nOvffeKwCIJ598MiTpoglTKULiiSeegBACf/mXf5m/5rpuwXv+5V/+BZlMpuDaO9/5TmWH/853vhPTp0/HCy+8kL/W39+PRx55BB/84AfR3t6ev37ddddhypQp+NGPfuTln9Ow7NixA5s2bcr/PPfcc7jyyivxyCOP4E//9E8Lxq+trQ3Tp08PUdroQ72pDyrRGx0tLS2YOXMment7gxG4DqDu1AfV6s7AwAAcxwlY2ogSsmHe0PzqV78SAMT//t//W/Ee7tmzR7S0tIjbbrut5H0GBgZEMpkUH/3oR/PXHn/8cQFA3H///cr73/72t4uVK1d68Sc0PLfddpvihRn/sW1b7NmzR/s5eoyrh3oTfarRm76+PnHkyBHxwgsviFtuuUUAEJ/5zGdCkD66UHeiTyW6M+4xnjJligAgksmkuPLKK8XLL78c4l9gPvQYh8i3vvUtrFq1CjfddJPy2vz583HTTTfhW9/6FkSJNPC77roLmUwG11xzTf7aeGFEd3e38v7u7m4cOHCgRunJOAsWLIAQAkIIuK6LG264AbFYDN/97ncxf/78sMWrO6g39UGlevOBD3wAM2fOxLJly3D77bfjYx/7GD7/+c+HIHl0oe7UB+Xqznge+d13342f/vSn+Ju/+Rts3rwZa9aswb59+0L8C8yGhnGI/P73v8cll1yC5uZmvPvd78a0adMKXr/44otx8OBBHD16tOg9tm7dir/927/FBz7wAVxyySX56yMjIwCAVCqlfKapqSn/OvGW9evX45577sG9996LP//zPw9bnLqEelN/lKM3X/nKV/D//t//w7/+67/iggsuQCaTQS6XC1jSaEPdqT8m050PfOADuPfee3Hdddfhqquuwhe/+EU8/PDDOHbsGP7+7/8+JInNJx62AI1MIpFAOp1GZ2cnfvWrXymvp9NpAIBt29rPv/jii/iTP/kTnHXWWfjOd75T8Np4der4PSYyOjqqVK8Sb5g3bx6+9a1v4brrrgtblLqFelN/lKM355xzTv7fH/zgB7Fy5Ur8xV/8BX784x8HIGF9QN2pPypdc97+9rdj9erV2vEnY9BjHCIXXHABHnjggaJej5///OdYvHixtmBr3759uPTSS9HR0YEHH3wQbW1tBa+Ph7N0vSYPHjyI2bNne/AXEJmbb74ZH/nIR8IWo66h3tQflepNMpnE+973PvzkJz+hJ7ICqDv1RzVrzrx5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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 3: Modify the high level of a square pulse inside a sequence\n", + "\n", + "#\n", + "\n", + "pulsebp = bb.BluePrint()\n", + "pulsebp.insertSegment(0, ramp, (0, 0), dur=5e-4)\n", + "pulsebp.insertSegment(1, ramp, (1, 1), dur=1e-3, name=\"varyme\")\n", + "pulsebp.insertSegment(2, \"waituntil\", 2e-3)\n", + "pulsebp.setSR(1e6)\n", + "\n", + "sinebp = bb.BluePrint()\n", + "sinebp.insertSegment(0, sine, (0.2e3, 0.5, 0.5, 0), dur=10e-3)\n", + "sinebp.setSR(1e6)\n", + "\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, pulsebp)\n", + "\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(1, sinebp)\n", + "\n", + "baseseq = bb.Sequence()\n", + "baseseq.setSR(1e6)\n", + "baseseq.addElement(1, elem1)\n", + "baseseq.addElement(2, elem2)\n", + "\n", + "baseseq.setSequenceSettings(1, 0, 20, 0, 0)\n", + "baseseq.setSequenceSettings(2, 0, 1, 0, 1)\n", + "\n", + "plotter(baseseq)\n", + "\n", + "# now vary this sequence\n", + "\n", + "poss = [1, 1]\n", + "channels = [1, 1]\n", + "names = [\"varyme\", \"varyme\"]\n", + "args = [\"start\", \"stop\"]\n", + "iters = [[1, 0.75, 0.5], [1, 0.75, 0.5]]\n", + "\n", + "newseq = bb.repeatAndVarySequence(baseseq, poss, channels, names, args, iters)\n", + "plotter(newseq)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Subsequences.html b/examples/Subsequences.html new file mode 100644 index 000000000..378cd646b --- /dev/null +++ b/examples/Subsequences.html @@ -0,0 +1,4581 @@ + + + + + + + + + Subsequences - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Subsequences

+

This notebook describes the use of subsequences.

+

Subsequences can be useful in a wide range of settings.

+
+
[1]:
+
+
+
%matplotlib notebook
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+sine = bb.PulseAtoms.sine
+ramp = bb.PulseAtoms.ramp
+
+
+
+
+

Example 1: Compression

+

In a waveform with very long “dead” periods, subsequences can – via the option to repeat elements – drastically reduce the number of points of the entire sequence.

+

Here we imagine a pulse sequence where we first wait, then perturb the system, then wait some more for readout. We’d like to vary the height of the perturbation.

+
+

Uncompressed

+
+
[2]:
+
+
+
# Uncompressed
+
+SR = 1e9
+t1 = 200e-6  # wait
+t2 = 20e-9  # perturb the system
+t3 = 250e-6  # read out
+
+bp1 = bb.BluePrint()
+bp1.insertSegment(0, ramp, (0, 0), dur=t1)
+bp1.insertSegment(1, ramp, (1, 1), dur=t2, name="perturbation")
+bp1.insertSegment(2, ramp, (0, 0), dur=t3)
+bp1.setSR(SR)
+
+
+
+
+
[3]:
+
+
+
plotter(bp1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[4]:
+
+
+
# Now make a variation of the height
+
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp1)
+
+elem2 = elem1.copy()
+elem2.changeArg(1, "perturbation", "start", 0.75)
+elem2.changeArg(1, "perturbation", "stop", 0.75)
+
+elem3 = elem1.copy()
+elem3.changeArg(1, "perturbation", "start", 0.5)
+elem3.changeArg(1, "perturbation", "stop", 0.5)
+
+# And put that together in a sequence
+seq = bb.Sequence()
+seq.addElement(1, elem1)
+seq.addElement(2, elem2)
+seq.addElement(3, elem3)
+seq.setSR(SR)
+
+
+
+
+
[5]:
+
+
+
plotter(seq)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[6]:
+
+
+
# The sequence is long and heavy on the memory
+seq.points
+
+
+
+
+
[6]:
+
+
+
+
+1350060
+
+
+
+
+

Compressed

+
+
[7]:
+
+
+
# Let's make a sequence instead of an element
+
+SR = 1e9
+t1 = 200e-6  # wait
+t2 = 20e-9  # perturb the system
+t3 = 250e-6  # read out
+
+compression = 100  # this number has to be chosen with some care
+
+bp1 = bb.BluePrint()
+bp1.insertSegment(0, ramp, (0, 0), dur=t1 / compression)
+bp1.setSR(SR)
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp1)
+#
+bp2 = bb.BluePrint()
+bp2.insertSegment(0, ramp, (1, 1), dur=t2, name="perturbation")
+bp2.setSR(SR)
+elem2 = bb.Element()
+elem2.addBluePrint(1, bp2)
+#
+bp3 = bb.BluePrint()
+bp3.insertSegment(0, ramp, (0, 0), dur=t3 / compression)
+bp3.setSR(SR)
+elem3 = bb.Element()
+elem3.addBluePrint(1, bp3)
+
+seq = bb.Sequence()
+seq.addElement(1, elem1)
+seq.setSequencingNumberOfRepetitions(1, compression)
+seq.addElement(2, elem2)
+seq.addElement(3, elem3)
+seq.setSequencingNumberOfRepetitions(3, compression)
+seq.setSR(SR)
+
+# Now make the variation
+seq2 = seq.copy()
+seq2.element(2).changeArg(1, "perturbation", "start", 0.75)
+seq2.element(2).changeArg(1, "perturbation", "stop", 0.75)
+#
+seq3 = seq.copy()
+seq3.element(2).changeArg(1, "perturbation", "start", 0.5)
+seq3.element(2).changeArg(1, "perturbation", "stop", 0.5)
+#
+fullseq = seq + seq2 + seq3
+plotter(fullseq)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[8]:
+
+
+
# The above sequence achieves the same as the uncompresed, but has fewer points
+fullseq.points
+
+
+
+
+
[8]:
+
+
+
+
+13560
+
+
+
+
+

Now using subsequences

+

Subsequences come into play when we want to, say, repeat each wait-perturb-wait element 25 times. In the uncompressed case, that can only be achieved by adding each element 24 times more, thus resulting in a very large output file. Using subsequences, we can get away with a much smaller file size.

+
+
[9]:
+
+
+
mainseq = bb.Sequence()
+mainseq.setSR(SR)
+
+mainseq.addSubSequence(1, seq)
+mainseq.addSubSequence(2, seq2)
+mainseq.addSubSequence(3, seq3)
+
+mainseq.setSequencingNumberOfRepetitions(1, 25)
+mainseq.setSequencingNumberOfRepetitions(2, 25)
+mainseq.setSequencingNumberOfRepetitions(3, 25)
+
+plotter(mainseq)
+
+# The plotting does not show the details of the subsequence,
+# but it DOES show the min and max voltages of a subsequence
+# as grey lines
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[10]:
+
+
+
# The number of points is still low
+mainseq.points
+
+
+
+
+
[10]:
+
+
+
+
+13560
+
+
+
+
[ ]:
+
+
+

+
+
+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Subsequences.ipynb b/examples/Subsequences.ipynb new file mode 100644 index 000000000..cbfee374d --- /dev/null +++ b/examples/Subsequences.ipynb @@ -0,0 +1,4401 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Subsequences\n", + "\n", + "This notebook describes the use of subsequences.\n", + "\n", + "Subsequences can be useful in a wide range of settings." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:01.021184Z", + "iopub.status.busy": "2025-01-20T06:28:01.020783Z", + "iopub.status.idle": "2025-01-20T06:28:01.408288Z", + "shell.execute_reply": "2025-01-20T06:28:01.407749Z" + } + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "sine = bb.PulseAtoms.sine\n", + "ramp = bb.PulseAtoms.ramp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example 1: Compression\n", + "\n", + "In a waveform with very long \"dead\" periods, subsequences can -- via the option to repeat elements -- drastically reduce the number of points of the entire sequence.\n", + "\n", + "Here we imagine a pulse sequence where we first wait, then perturb the system, then wait some more for readout. We'd like to vary the height of the perturbation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uncompressed" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:01.410573Z", + "iopub.status.busy": "2025-01-20T06:28:01.410141Z", + "iopub.status.idle": "2025-01-20T06:28:01.414581Z", + "shell.execute_reply": "2025-01-20T06:28:01.414123Z" + } + }, + "outputs": [], + "source": [ + "# Uncompressed\n", + "\n", + "SR = 1e9\n", + "t1 = 200e-6 # wait\n", + "t2 = 20e-9 # perturb the system\n", + "t3 = 250e-6 # read out\n", + "\n", + "bp1 = bb.BluePrint()\n", + "bp1.insertSegment(0, ramp, (0, 0), dur=t1)\n", + "bp1.insertSegment(1, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", + "bp1.insertSegment(2, ramp, (0, 0), dur=t3)\n", + "bp1.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:01.416295Z", + "iopub.status.busy": "2025-01-20T06:28:01.415955Z", + "iopub.status.idle": "2025-01-20T06:28:01.644636Z", + "shell.execute_reply": "2025-01-20T06:28:01.644107Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(bp1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:01.646613Z", + "iopub.status.busy": "2025-01-20T06:28:01.646262Z", + "iopub.status.idle": "2025-01-20T06:28:01.651212Z", + "shell.execute_reply": "2025-01-20T06:28:01.650778Z" + } + }, + "outputs": [], + "source": [ + "# Now make a variation of the height\n", + "\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "\n", + "elem2 = elem1.copy()\n", + "elem2.changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "elem2.changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "\n", + "elem3 = elem1.copy()\n", + "elem3.changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "elem3.changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "\n", + "# And put that together in a sequence\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:01.653015Z", + "iopub.status.busy": "2025-01-20T06:28:01.652604Z", + "iopub.status.idle": "2025-01-20T06:28:02.252015Z", + "shell.execute_reply": "2025-01-20T06:28:02.251366Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(seq)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:02.254030Z", + "iopub.status.busy": "2025-01-20T06:28:02.253674Z", + "iopub.status.idle": "2025-01-20T06:28:02.257851Z", + "shell.execute_reply": "2025-01-20T06:28:02.257336Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1350060" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The sequence is long and heavy on the memory\n", + "seq.points" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compressed" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:02.259492Z", + "iopub.status.busy": "2025-01-20T06:28:02.259163Z", + "iopub.status.idle": "2025-01-20T06:28:02.362867Z", + "shell.execute_reply": "2025-01-20T06:28:02.362379Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Let's make a sequence instead of an element\n", + "\n", + "SR = 1e9\n", + "t1 = 200e-6 # wait\n", + "t2 = 20e-9 # perturb the system\n", + "t3 = 250e-6 # read out\n", + "\n", + "compression = 100 # this number has to be chosen with some care\n", + "\n", + "bp1 = bb.BluePrint()\n", + "bp1.insertSegment(0, ramp, (0, 0), dur=t1 / compression)\n", + "bp1.setSR(SR)\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "#\n", + "bp2 = bb.BluePrint()\n", + "bp2.insertSegment(0, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", + "bp2.setSR(SR)\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(1, bp2)\n", + "#\n", + "bp3 = bb.BluePrint()\n", + "bp3.insertSegment(0, ramp, (0, 0), dur=t3 / compression)\n", + "bp3.setSR(SR)\n", + "elem3 = bb.Element()\n", + "elem3.addBluePrint(1, bp3)\n", + "\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.setSequencingNumberOfRepetitions(1, compression)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSequencingNumberOfRepetitions(3, compression)\n", + "seq.setSR(SR)\n", + "\n", + "# Now make the variation\n", + "seq2 = seq.copy()\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "#\n", + "seq3 = seq.copy()\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "#\n", + "fullseq = seq + seq2 + seq3\n", + "plotter(fullseq)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:02.364538Z", + "iopub.status.busy": "2025-01-20T06:28:02.364195Z", + "iopub.status.idle": "2025-01-20T06:28:02.368825Z", + "shell.execute_reply": "2025-01-20T06:28:02.368240Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "13560" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The above sequence achieves the same as the uncompresed, but has fewer points\n", + "fullseq.points" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Now using subsequences\n", + "\n", + "Subsequences come into play when we want to, say, repeat each wait-perturb-wait element 25 times.\n", + "In the uncompressed case, that can only be achieved by adding each element 24 times more, thus resulting in a very large output file. Using subsequences, we can get away with a much smaller file size." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:02.370547Z", + "iopub.status.busy": "2025-01-20T06:28:02.370245Z", + "iopub.status.idle": "2025-01-20T06:28:02.409504Z", + "shell.execute_reply": "2025-01-20T06:28:02.408911Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mainseq = bb.Sequence()\n", + "mainseq.setSR(SR)\n", + "\n", + "mainseq.addSubSequence(1, seq)\n", + "mainseq.addSubSequence(2, seq2)\n", + "mainseq.addSubSequence(3, seq3)\n", + "\n", + "mainseq.setSequencingNumberOfRepetitions(1, 25)\n", + "mainseq.setSequencingNumberOfRepetitions(2, 25)\n", + "mainseq.setSequencingNumberOfRepetitions(3, 25)\n", + "\n", + "plotter(mainseq)\n", + "\n", + "# The plotting does not show the details of the subsequence,\n", + "# but it DOES show the min and max voltages of a subsequence\n", + "# as grey lines" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-20T06:28:02.411661Z", + "iopub.status.busy": "2025-01-20T06:28:02.411481Z", + "iopub.status.idle": "2025-01-20T06:28:02.416102Z", + "shell.execute_reply": "2025-01-20T06:28:02.415551Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "13560" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The number of points is still low\n", + "mainseq.points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 000000000..41831c93a --- /dev/null +++ b/examples/index.html @@ -0,0 +1,341 @@ + + + + + + + + + Broadbean Examples - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 000000000..edfe63723 --- /dev/null +++ b/genindex.html @@ -0,0 +1,743 @@ + + + + + + + Index - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+ +
+

Index

+
A | B | C | D | E | F | G | I | L | M | N | O | P | R | S | V | W
+
+
+

A

+ + + +
+
+ +
+

B

+ + + +
    +
  • + broadbean.plotting + +
  • +
  • + broadbean.ripasso + +
  • +
  • + broadbean.sequence + +
  • +
  • + broadbean.tools + +
  • +
+
+ +
+

C

+ + + +
+
+ +
+

D

+ + + +
+
+ +
+

E

+ + + +
+
+ +
+

F

+ + +
+
+ +
+

G

+ + + +
+
+ +
+

I

+ + + +
+
+ +
+

L

+ + + +
+
+ +
+

M

+ + +
+
+ +
+

N

+ + +
+
+ +
+

O

+ + + +
+
+ +
+

P

+ + + +
+
+ +
+

R

+ + + +
+
+ +
+

S

+ + + +
+
+ +
+

V

+ + +
+
+ +
+

W

+ + +
+
+ + +
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 000000000..84d04825d --- /dev/null +++ b/index.html @@ -0,0 +1,395 @@ + + + + + + + + + broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+

Welcome to broadbean’s documentation!

+

The broadbean package is a tool for creating and manipulating pulse +sequences.The broadbean module lets the user compose and manipulate pulse sequences. +The aim of the module is to reduce pulse building to the logical minimum of +specifications so that building and manipulation become as easy as saying “Gimme +a square wave, then a ramp, then a sine, and then wait for 10 ms” and, in particular, +“Do the same thing again, but now with the sine having twice the frequency it had before”.

+

The little extra module called ripasso performs frequency filtering and frequency filter +compensation. It could be useful in a general setting and is therefore factored out +to its own module.

+

The name: The broad bean is one of my favourite pulses.

+
+

Documentation

+ +
+

Indices and tables

+ +
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..b67c95861f97324e5744c90987d9de04ef9581c9 GIT binary patch literal 4152 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 CY%Ns) literal 0 HcmV?d00001 diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 000000000..fe8a32efc --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,357 @@ + + + + + + + Python Module Index - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+ +
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 000000000..663620d61 --- /dev/null +++ b/search.html @@ -0,0 +1,311 @@ + + + + + + + + + +Search - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+ + + +
+ +
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 000000000..c1bac9d4a --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Basic blueprinting": [[7, "Basic-blueprinting"]], "Behind The Scenes:": [[2, "behind-the-scenes"]], "Blueprints": [[7, "Blueprints"]], "Breaking Changes:": [[1, "breaking-changes"], [2, "breaking-changes"]], "Broadbean Examples": [[9, null]], "Building a BluePrint": [[4, "Building-a-BluePrint"]], "Changelog for broadbean 0.10.0": [[1, null]], "Changelog for broadbean 0.11.0": [[2, null]], "Changelogs": [[3, null]], "Compressed": [[8, "Compressed"]], "Create a broadbean environment": [[11, "create-a-broadbean-environment"]], "Delays and filter compensation": [[7, "Delays-and-filter-compensation"]], "Documentation": [[10, null]], "Ekstras": [[4, "Ekstras"]], "Elements": [[7, "Elements"]], "Example 1: Compression": [[8, "Example-1:-Compression"]], "Filter compensation with the ripasso module": [[5, null]], "Getting Started": [[11, null]], "Improved:": [[2, "improved"]], "Indices and tables": [[10, "indices-and-tables"]], "Installation": [[11, "installation"]], "Installing the latest broadbean release": [[11, "installing-the-latest-broadbean-release"]], "Intro to Blueprints:": [[7, "Intro-to-Blueprints:"]], "Intro to Elements:": [[7, "Intro-to-Elements:"]], "Intro to Normal segments:": [[7, "Intro-to-Normal-segments:"]], "Intro to Sequences:": [[7, "Intro-to-Sequences:"]], "Intro to Special segments:": [[7, "Intro-to-Special-segments:"]], "Introduction": [[6, null]], "Keeping your environment up to date": [[11, "keeping-your-environment-up-to-date"]], "Lingo": [[7, "Lingo"]], "Markers": [[7, "Markers"]], "Modifying blueprints": [[7, "Modifying-blueprints"]], "Module contents": [[0, "module-broadbean"]], "New:": [[1, "new"], [2, "new"]], "Now using subsequences": [[8, "Now-using-subsequences"]], "Pulsebuilding tutorial": [[7, null]], "RC Filters": [[5, "RC-Filters"]], "Read and Write BluePrint from JSON file Tutorial": [[4, "Read-and-Write-BluePrint-from-JSON-file-Tutorial"]], "Read and Write Element from JSON file Tutorial": [[4, "Read-and-Write-Element-from-JSON-file-Tutorial"]], "Read and Write Sequence from JSON file Tutorial": [[4, "Read-and-Write-Sequence-from-JSON-file-Tutorial"]], "Read and Write from JSON file Tutorial": [[4, null]], "Reading the Sequence back from the file": [[4, "Reading-the-Sequence-back-from-the-file"]], "Requirements": [[11, "requirements"]], "Segments:": [[7, "Segments:"]], "Sequences": [[7, "Sequences"]], "Sequences varying parameters": [[7, "Sequences-varying-parameters"]], "Setting up your development environment": [[11, "setting-up-your-development-environment"]], "Special segments": [[7, "Special-segments"]], "Step 1: Make a sequence": [[6, "Step-1:-Make-a-sequence"]], "Step 2: Initialise the driver": [[6, "Step-2:-Initialise-the-driver"]], "Step 3: Make output for the driver": [[6, "Step-3:-Make-output-for-the-driver"]], "Step 4: Build, send, load, and assign a .seqx file": [[6, "Step-4:-Build,-send,-load,-and-assign-a-.seqx-file"]], "Step 5: Play it": [[6, "Step-5:-Play-it"]], "Submodules": [[0, "submodules"]], "Subsequences": [[8, null]], "Table of Contents": [[7, "Table-of-Contents"]], "Tektronix AWG 5014 output": [[7, "Tektronix-AWG-5014-output"]], "Test if the Sequences are identical": [[4, "Test-if-the-Sequences-are-identical"]], "Uncompressed": [[8, "Uncompressed"]], "Updating Broadbean": [[11, "updating-broadbean"]], "User-supplied transfer function": [[5, "User-supplied-transfer-function"]], "Using broadbean": [[11, "using-broadbean"]], "Writing the Sequence to a file": [[4, "Writing-the-Sequence-to-a-file"]], "broadbean package": [[0, null]], "broadbean.blueprint module": [[0, "module-broadbean.blueprint"]], "broadbean.broadbean module": [[0, "module-broadbean.broadbean"]], "broadbean.element module": [[0, "module-broadbean.element"]], "broadbean.plotting module": [[0, "module-broadbean.plotting"]], "broadbean.ripasso module": [[0, "module-broadbean.ripasso"]], "broadbean.sequence module": [[0, "module-broadbean.sequence"]], "broadbean.tools module": [[0, "module-broadbean.tools"]]}, "docnames": ["api/generated/broadbean", "changes/0.10.0", "changes/0.11.0", "changes/index", "examples/Example_Write_Read_JSON", "examples/Filter_compensation", "examples/Making_output_for_Tektronix_AWG70000A", "examples/Pulse_Building_Tutorial", "examples/Subsequences", "examples/index", "index", "start/index"], "envversion": {"nbsphinx": 4, "sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1}, "filenames": ["api/generated/broadbean.rst", "changes/0.10.0.rst", "changes/0.11.0.rst", "changes/index.rst", "examples/Example_Write_Read_JSON.ipynb", "examples/Filter_compensation.ipynb", "examples/Making_output_for_Tektronix_AWG70000A.ipynb", "examples/Pulse_Building_Tutorial.ipynb", "examples/Subsequences.ipynb", "examples/index.rst", "index.rst", "start/index.rst"], "indexentries": {"addarray() (broadbean.element.element method)": [[0, "broadbean.element.Element.addArray", false]], "addblueprint() (broadbean.element.element method)": [[0, "broadbean.element.Element.addBluePrint", false]], "addelement() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.addElement", false]], "addflags() (broadbean.element.element method)": [[0, "broadbean.element.Element.addFlags", false]], "addsubsequence() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.addSubSequence", false]], "applycustomtransferfunction() (in module broadbean.ripasso)": [[0, "broadbean.ripasso.applyCustomTransferFunction", false]], "applyinversercfilter() (in module broadbean.ripasso)": [[0, "broadbean.ripasso.applyInverseRCFilter", false]], "applyrcfilter() (in module broadbean.ripasso)": [[0, "broadbean.ripasso.applyRCFilter", false]], "arb_func() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.arb_func", false]], "blueprint (class in broadbean.blueprint)": [[0, "broadbean.blueprint.BluePrint", false]], "blueprint_from_description() (broadbean.blueprint.blueprint class method)": [[0, "broadbean.blueprint.BluePrint.blueprint_from_description", false]], "broadbean": [[0, "module-broadbean", false]], "broadbean.blueprint": [[0, "module-broadbean.blueprint", false]], "broadbean.broadbean": [[0, "module-broadbean.broadbean", false]], "broadbean.element": [[0, "module-broadbean.element", false]], "broadbean.plotting": [[0, "module-broadbean.plotting", false]], "broadbean.ripasso": [[0, "module-broadbean.ripasso", false]], "broadbean.sequence": [[0, "module-broadbean.sequence", false]], "broadbean.tools": [[0, "module-broadbean.tools", false]], "changearg() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.changeArg", false]], "changearg() (broadbean.element.element method)": [[0, "broadbean.element.Element.changeArg", false]], "changeduration() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.changeDuration", false]], "changeduration() (broadbean.element.element method)": [[0, "broadbean.element.Element.changeDuration", false]], "channels (broadbean.element.element property)": [[0, "broadbean.element.Element.channels", false]], "channels (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.channels", false]], "checkconsistency() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.checkConsistency", false]], "copy() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.copy", false]], "copy() (broadbean.element.element method)": [[0, "broadbean.element.Element.copy", false]], "copy() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.copy", false]], "description (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.description", false]], "description (broadbean.element.element property)": [[0, "broadbean.element.Element.description", false]], "description (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.description", false]], "duration (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.duration", false]], "duration (broadbean.element.element property)": [[0, "broadbean.element.Element.duration", false]], "duration (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.duration", false]], "durations (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.durations", false]], "element (class in broadbean.element)": [[0, "broadbean.element.Element", false]], "element() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.element", false]], "element_from_description() (broadbean.element.element class method)": [[0, "broadbean.element.Element.element_from_description", false]], "elementdurationerror": [[0, "broadbean.element.ElementDurationError", false]], "forge() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.forge", false]], "gaussian() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.gaussian", false]], "gaussian_smooth_cutoff() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.gaussian_smooth_cutoff", false]], "getarrays() (broadbean.element.element method)": [[0, "broadbean.element.Element.getArrays", false]], "getsiscalingandprefix() (in module broadbean.plotting)": [[0, "broadbean.plotting.getSIScalingAndPrefix", false]], "init_from_json() (broadbean.blueprint.blueprint class method)": [[0, "broadbean.blueprint.BluePrint.init_from_json", false]], "init_from_json() (broadbean.element.element class method)": [[0, "broadbean.element.Element.init_from_json", false]], "init_from_json() (broadbean.sequence.sequence class method)": [[0, "broadbean.sequence.Sequence.init_from_json", false]], "insertsegment() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.insertSegment", false]], "invalidforgedsequenceerror": [[0, "broadbean.sequence.InvalidForgedSequenceError", false]], "length_segments (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.length_segments", false]], "length_sequenceelements (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.length_sequenceelements", false]], "makelinearlyvaryingsequence() (in module broadbean.tools)": [[0, "broadbean.tools.makeLinearlyVaryingSequence", false]], "makevaryingsequence() (in module broadbean.tools)": [[0, "broadbean.tools.makeVaryingSequence", false]], "marked_for_deletion() (in module broadbean.broadbean)": [[0, "broadbean.broadbean.marked_for_deletion", false]], "missingfrequencieserror": [[0, "broadbean.ripasso.MissingFrequenciesError", false]], "module": [[0, "module-broadbean", false], [0, "module-broadbean.blueprint", false], [0, "module-broadbean.broadbean", false], [0, "module-broadbean.element", false], [0, "module-broadbean.plotting", false], [0, "module-broadbean.ripasso", false], [0, "module-broadbean.sequence", false], [0, "module-broadbean.tools", false]], "name (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.name", false]], "outputforawgfile() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.outputForAWGFile", false]], "outputforseqxfile() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.outputForSEQXFile", false]], "outputforseqxfilewithflags() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.outputForSEQXFileWithFlags", false]], "plotter() (in module broadbean.plotting)": [[0, "broadbean.plotting.plotter", false]], "points (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.points", false]], "points (broadbean.element.element property)": [[0, "broadbean.element.Element.points", false]], "points (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.points", false]], "pulseatoms (class in broadbean.broadbean)": [[0, "broadbean.broadbean.PulseAtoms", false]], "ramp() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.ramp", false]], "removesegment() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.removeSegment", false]], "removesegmentmarker() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.removeSegmentMarker", false]], "repeatandvarysequence() (in module broadbean.tools)": [[0, "broadbean.tools.repeatAndVarySequence", false]], "segmentdurationerror": [[0, "broadbean.blueprint.SegmentDurationError", false]], "sequence (class in broadbean.sequence)": [[0, "broadbean.sequence.Sequence", false]], "sequence_from_description() (broadbean.sequence.sequence class method)": [[0, "broadbean.sequence.Sequence.sequence_from_description", false]], "sequencecompatibilityerror": [[0, "broadbean.sequence.SequenceCompatibilityError", false]], "sequenceconsistencyerror": [[0, "broadbean.sequence.SequenceConsistencyError", false]], "sequencingerror": [[0, "broadbean.sequence.SequencingError", false]], "setchannelamplitude() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelAmplitude", false]], "setchanneldelay() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelDelay", false]], "setchannelfiltercompensation() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelFilterCompensation", false]], "setchanneloffset() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelOffset", false]], "setchannelvoltagerange() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelVoltageRange", false]], "setsegmentmarker() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.setSegmentMarker", false]], "setsequencesettings() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequenceSettings", false]], "setsequencingeventinput() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingEventInput", false]], "setsequencingeventjumptarget() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingEventJumpTarget", false]], "setsequencinggoto() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingGoto", false]], "setsequencingnumberofrepetitions() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingNumberOfRepetitions", false]], "setsequencingtriggerwait() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingTriggerWait", false]], "setsr() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.setSR", false]], "setsr() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSR", false]], "showprint() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.showPrint", false]], "sine() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.sine", false]], "specificationinconsistencyerror": [[0, "broadbean.sequence.SpecificationInconsistencyError", false]], "sr (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.SR", false]], "sr (broadbean.element.element property)": [[0, "broadbean.element.Element.SR", false]], "sr (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.SR", false]], "validatedurations() (broadbean.element.element method)": [[0, "broadbean.element.Element.validateDurations", false]], "waituntil() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.waituntil", false]], "write_to_json() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.write_to_json", false]], "write_to_json() (broadbean.element.element method)": [[0, "broadbean.element.Element.write_to_json", false]], "write_to_json() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.write_to_json", false]]}, "objects": {"": [[0, 0, 0, "-", "broadbean"]], "broadbean": [[0, 0, 0, "-", "blueprint"], [0, 0, 0, "-", "broadbean"], [0, 0, 0, "-", "element"], [0, 0, 0, "-", "plotting"], [0, 0, 0, "-", "ripasso"], [0, 0, 0, "-", "sequence"], [0, 0, 0, "-", "tools"]], "broadbean.blueprint": [[0, 1, 1, "", "BluePrint"], [0, 4, 1, "", "SegmentDurationError"]], "broadbean.blueprint.BluePrint": [[0, 2, 1, "", "SR"], [0, 3, 1, "", "blueprint_from_description"], [0, 3, 1, "", "changeArg"], [0, 3, 1, "", "changeDuration"], [0, 3, 1, "", "copy"], [0, 2, 1, "", "description"], [0, 2, 1, "", "duration"], [0, 2, 1, "", "durations"], [0, 3, 1, "", "init_from_json"], [0, 3, 1, "", "insertSegment"], [0, 2, 1, "", "length_segments"], [0, 2, 1, "", "points"], [0, 3, 1, "", "removeSegment"], [0, 3, 1, "", "removeSegmentMarker"], [0, 3, 1, "", "setSR"], [0, 3, 1, "", "setSegmentMarker"], [0, 3, 1, "", "showPrint"], [0, 3, 1, "", "write_to_json"]], "broadbean.broadbean": [[0, 1, 1, "", "PulseAtoms"], [0, 5, 1, "", "marked_for_deletion"]], "broadbean.broadbean.PulseAtoms": [[0, 3, 1, "", "arb_func"], [0, 3, 1, "", "gaussian"], [0, 3, 1, "", "gaussian_smooth_cutoff"], [0, 3, 1, "", "ramp"], [0, 3, 1, "", "sine"], [0, 3, 1, "", "waituntil"]], "broadbean.element": [[0, 1, 1, "", "Element"], [0, 4, 1, "", "ElementDurationError"]], "broadbean.element.Element": [[0, 2, 1, "", "SR"], [0, 3, 1, "", "addArray"], [0, 3, 1, "", "addBluePrint"], [0, 3, 1, "", "addFlags"], [0, 3, 1, "", "changeArg"], [0, 3, 1, "", "changeDuration"], [0, 2, 1, "", "channels"], [0, 3, 1, "", "copy"], [0, 2, 1, "", "description"], [0, 2, 1, "", "duration"], [0, 3, 1, "", "element_from_description"], [0, 3, 1, "", "getArrays"], [0, 3, 1, "", "init_from_json"], [0, 2, 1, "", "points"], [0, 3, 1, "", "validateDurations"], [0, 3, 1, "", "write_to_json"]], "broadbean.plotting": [[0, 5, 1, "", "getSIScalingAndPrefix"], [0, 5, 1, "", "plotter"]], "broadbean.ripasso": [[0, 4, 1, "", "MissingFrequenciesError"], [0, 5, 1, "", "applyCustomTransferFunction"], [0, 5, 1, "", "applyInverseRCFilter"], [0, 5, 1, "", "applyRCFilter"]], "broadbean.sequence": [[0, 4, 1, "", "InvalidForgedSequenceError"], [0, 1, 1, "", "Sequence"], [0, 4, 1, "", "SequenceCompatibilityError"], [0, 4, 1, "", "SequenceConsistencyError"], [0, 4, 1, "", "SequencingError"], [0, 4, 1, "", "SpecificationInconsistencyError"]], "broadbean.sequence.Sequence": [[0, 2, 1, "", "SR"], [0, 3, 1, "", "addElement"], [0, 3, 1, "", "addSubSequence"], [0, 2, 1, "", "channels"], [0, 3, 1, "", "checkConsistency"], [0, 3, 1, "", "copy"], [0, 2, 1, "", "description"], [0, 2, 1, "", "duration"], [0, 3, 1, "", "element"], [0, 3, 1, "", "forge"], [0, 3, 1, "", "init_from_json"], [0, 2, 1, "", "length_sequenceelements"], [0, 2, 1, "", "name"], [0, 3, 1, "", "outputForAWGFile"], [0, 3, 1, "", "outputForSEQXFile"], [0, 3, 1, "", "outputForSEQXFileWithFlags"], [0, 2, 1, "", "points"], [0, 3, 1, "", "sequence_from_description"], [0, 3, 1, "", "setChannelAmplitude"], [0, 3, 1, "", "setChannelDelay"], [0, 3, 1, "", "setChannelFilterCompensation"], [0, 3, 1, "", "setChannelOffset"], [0, 3, 1, "", "setChannelVoltageRange"], [0, 3, 1, "", "setSR"], [0, 3, 1, "", "setSequenceSettings"], [0, 3, 1, "", "setSequencingEventInput"], [0, 3, 1, "", "setSequencingEventJumpTarget"], [0, 3, 1, "", "setSequencingGoto"], [0, 3, 1, "", "setSequencingNumberOfRepetitions"], [0, 3, 1, "", "setSequencingTriggerWait"], [0, 3, 1, "", "write_to_json"]], "broadbean.tools": [[0, 5, 1, "", "makeLinearlyVaryingSequence"], [0, 5, 1, "", "makeVaryingSequence"], [0, 5, 1, "", "repeatAndVarySequence"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "property", "Python property"], "3": ["py", "method", "Python method"], "4": ["py", "exception", "Python exception"], "5": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:property", "3": "py:method", "4": "py:exception", "5": "py:function"}, "terms": {"": [0, 2, 5, 6, 7, 8, 10], "0": [0, 3, 4, 5, 6, 7, 8, 10], "001": 7, "01": 5, "02": 5, "05": 6, "06": 7, "07": 7, "0x7f59ac07bf60": 7, "1": [0, 1, 4, 5, 7], "10": [2, 3, 4, 5, 6, 7, 8, 10, 11], "100": [7, 8], "1000": 5, "10000": 7, "1000000000": 7, "100e": [4, 7], "101": 2, "102": 2, "103": 2, "107": 2, "108": 2, "109": 2, "10e": [4, 6, 7], "10e3": 5, "11": [3, 4, 6, 7, 10], "110": 2, "111": 2, "12": [4, 5, 6, 7], "123e": 7, "13": [4, 6, 7], "1350060": 8, "13560": 8, "139": 2, "14": [4, 7], "15": [4, 7], "150e": 6, "158": 2, "16": [4, 7], "17": [4, 7], "172": [6, 7], "18": [1, 4, 7], "19": [4, 7], "190": 7, "1e": [0, 4, 7], "1e3": 0, "1e6": [0, 7], "1e9": [4, 6, 7, 8], "2": [0, 4, 5, 7, 8], "20": [6, 7], "2000": 5, "200e": 8, "2021": 1, "2022": 2, "20e": 8, "21": 7, "22": 7, "23": 7, "24": 8, "243": 6, "25": 8, "250e": 8, "25e": [6, 7], "25e9": 6, "2e": [0, 7], "2e3": 7, "2e9": 6, "2puls": 7, "3": [0, 2, 4, 5, 7, 8, 11], "300e": [4, 7], "333e6": [4, 7], "339966": 5, "34": [6, 7], "39": [6, 7], "3e": 7, "4": [0, 4, 5, 7, 8], "40": 7, "5": [4, 5, 7, 8], "500": 5, "500000": 7, "500000000": 7, "5014": 0, "517": 2, "57": 7, "5e": [4, 6, 7], "5e5": 7, "5e8": 7, "6": [0, 2, 4, 5, 6, 7, 8], "675": 7, "67e6": 7, "6e": 7, "7": [2, 4, 5, 6, 7, 8, 11], "70000a": 0, "75": [4, 7, 8], "8": [4, 5, 6, 7, 8], "880": 6, "881": 6, "882": 6, "883": 6, "884": 6, "886": 6, "887": 6, "9": [4, 5, 6, 7, 8, 11], "971": 6, "972": 6, "973": 6, "984": 6, "985": 6, "987": 6, "988": 6, "989": 6, "990": 6, "990033": 5, "991": 6, "999999999999999e": 7, "A": [0, 4, 5, 6, 7], "AND": 7, "And": [5, 6, 7, 8], "As": [5, 11], "At": 7, "By": 7, "For": [0, 7, 11], "If": [0, 6, 7, 11], "In": [5, 6, 7, 8, 11], "It": [7, 10], "NO": [4, 7], "NOT": 7, "No": [0, 6], "Not": [0, 7], "ON": [0, 4, 7], "On": 7, "One": 0, "Or": 5, "That": 7, "The": [0, 1, 3, 4, 5, 6, 7, 8, 10, 11], "Then": 5, "There": [1, 11], "These": 7, "To": [7, 11], "Will": 7, "_amplitud": 6, "_awg_fil": 0, "_awgspec": 6, "_data": 4, "_prepareforoutput": 6, "_subelementbuild": 0, "ab": 0, "abl": 11, "about": 0, "abov": [7, 8, 11], "absolut": 7, "accord": [0, 11], "achiev": [7, 8], "action": 2, "activ": [6, 11], "ad": [0, 4, 7, 8], "add": [0, 6, 7], "addarrai": 0, "addblueprint": [0, 4, 6, 7, 8], "addel": [0, 4, 6, 7, 8], "addflag": 0, "addit": 0, "addsubsequ": [0, 8], "adjust": 11, "after": [0, 7], "again": [7, 10, 11], "aim": 10, "all": [0, 7], "allow": [0, 7], "along": 0, "also": [0, 7, 11], "alwai": 0, "ambigu": 0, "amount": 7, "ampkei": 6, "ampl": [0, 4, 5, 7], "amplitud": [0, 4, 6, 7], "an": [0, 5, 6, 7, 8, 11], "anaconda": 11, "ani": [0, 7, 11], "anoth": 7, "anyth": 0, "append": [0, 7], "appli": [0, 5, 6, 7], "applic": 11, "apply_delai": [0, 7], "apply_filt": [0, 7], "applycustomtransferfunct": [0, 5], "applyinversercfilt": [0, 5], "applyrcfilt": [0, 5], "appropri": [0, 4, 7], "appveyor": 2, "ar": [0, 1, 5, 7, 11], "arb": 5, "arb_func": [0, 7], "arbitrari": 0, "arbitrati": 7, "arg": [0, 4, 7], "argslist": 0, "argument": [0, 7], "arrai": [0, 4, 5, 7], "assert": [0, 6], "assign": 0, "associ": [0, 7], "assum": [0, 11], "attach": 4, "august": 2, "auto": 7, "automat": 2, "auxiliari": 2, "averag": 5, "awai": 8, "awg": [0, 6], "awg1": 7, "awg5014": [0, 7], "awg70000": 2, "awg70000a": [0, 6], "awg70002a": 6, "awg_amplitud": 6, "ax": 5, "axi": [0, 7], "azur": 11, "back": [5, 6, 7], "backend": 0, "base": [0, 6, 7, 11], "basebp": 7, "baseel": 0, "baseelem": [6, 7], "baseseq": 7, "baseshap": 6, "bash": 11, "basic": 0, "bb": [4, 6, 7, 8], "bean": 10, "becaus": 5, "becom": [0, 7, 10], "been": [0, 2, 6, 11], "befor": [0, 7, 10, 11], "behind": 3, "being": [4, 7, 11], "below": [0, 7], "besid": 0, "best": 5, "big": 7, "binari": 6, "bind": 0, "bit": [6, 7], "bleak": 7, "blue": [4, 7], "blue_dict": 0, "blueprint": [2, 6, 8, 9, 10], "blueprint_from_descript": 0, "blueprintplott": 2, "bluprint": 1, "bool": [0, 7], "both": [0, 5, 7, 11], "both_chans_awg_input": 7, "bottom": [4, 7], "bound": 0, "box": 4, "bp1": [7, 8], "bp2": [7, 8], "bp3": 8, "bp_atm": 7, "bp_box": [4, 7], "bp_boxes_read": 4, "bp_mod": 7, "bp_one_two": 4, "bp_rtm": [4, 7], "bp_sine": [4, 7], "bp_sineandbox": [4, 7], "bp_squar": [4, 7], "bp_wait": 7, "break": 3, "broad": 10, "broadbean": [4, 5, 6, 7, 8, 10], "broken": 11, "browser": 11, "bsic": 6, "build": [2, 10], "built": [0, 5, 6, 11], "busi": 0, "button": 11, "c": 11, "cabl": 0, "call": [0, 4, 6, 7, 10], "callabl": 0, "can": [0, 6, 7, 8, 11], "capabl": 11, "care": [5, 8, 11], "case": [0, 7, 8], "cd": 11, "cdot": 5, "cell": 6, "centr": 0, "certain": 7, "cfg": 2, "ch1": 6, "chain": 7, "chan": 6, "chan1_awg_input": 7, "chan3_awg_input": 7, "chanc": 11, "chang": [0, 3, 7, 11], "changearg": [0, 6, 7, 8], "changedur": [0, 7], "changelog": 10, "channel": [0, 4, 6, 7], "check": [0, 5, 6, 7, 11], "checkconsist": [0, 7], "choos": 11, "chosen": 8, "chronolog": 7, "chxx_amp": 0, "chxx_offset": 0, "circuit": [0, 5], "class": 0, "classmethod": 0, "clear": 0, "click": 11, "clone": 11, "close": [6, 11], "co": 5, "collect": [0, 4, 7], "color": 5, "com": 7, "come": [4, 7, 8], "command": 11, "compar": 5, "comparison": 2, "compat": 7, "compatipl": 1, "compens": [0, 9, 10], "compil": 6, "compos": 10, "comput": 11, "concern": 0, "conda": [2, 11], "config": 2, "configur": 11, "conflict": 7, "consist": [0, 6, 7], "constant": 0, "construct": [0, 7], "contain": [0, 4, 7], "content": 10, "continu": 6, "convers": 5, "convert": 2, "convolv": 5, "copi": [0, 6, 8], "correct": [0, 6, 7], "correctli": [6, 11], "correspond": 0, "correspondingli": 0, "could": 10, "count": [0, 7], "creat": [4, 7, 10], "current": [0, 7], "curv": 5, "custom": 0, "cut": [5, 7], "cut_off": 0, "cutoff": 0, "d": 8, "data": 0, "dc": [0, 5, 7], "dcgain": [0, 5], "dead": 8, "decid": 11, "decor": 0, "def": [4, 5], "default": [0, 7, 11], "defin": [0, 6], "delai": [0, 4, 6], "delet": 2, "depend": [0, 2, 11], "dependabot": 2, "deprec": 7, "describ": [0, 7, 8], "descript": [0, 4], "design": 7, "desir": 7, "desktop": 11, "detail": 8, "devop": 11, "dict": [0, 7], "dictionari": 0, "differ": [0, 4, 7], "dimensionless": 0, "directli": [4, 7], "discard": 0, "discript": 4, "displai": 7, "disregard": 0, "distorion": 0, "distort": 7, "distribut": 2, "do": [4, 5, 6, 7, 10], "document": [2, 11], "doe": [0, 7, 8, 11], "domain": 5, "don": [0, 7], "done": [5, 6], "down": 6, "download": 11, "drastic": [5, 8], "drive": 6, "driver": [0, 7], "dummi": 0, "dur": [0, 4, 6, 7, 8], "durat": [0, 4, 7], "durslist": 0, "e": [0, 4, 5, 6, 7, 11], "each": [0, 4, 6, 7, 8], "easi": [7, 10], "easili": 7, "edit": 11, "effect": [5, 11], "either": [0, 5, 6, 7, 11], "ekstra": [9, 10], "elem": [4, 6], "elem1": [4, 7, 8], "elem2": [4, 7, 8], "elem3": 8, "elem_read": 4, "element": [1, 2, 6, 8, 9, 10], "element_dict": 0, "element_from_descript": 0, "elementdurationerror": [0, 7], "elemtmp": 4, "els": 0, "enabl": 2, "end": [0, 4, 7], "endpoint": 0, "enough": 7, "ensur": [0, 6, 7, 11], "ensureaverage_fixed_level": 0, "enter": 11, "entir": 8, "enumer": 6, "env": 11, "equival": 7, "error": [0, 7], "especi": 11, "essenti": 7, "etc": [0, 7], "even": 7, "evenli": 0, "event": 0, "event_jump": 0, "event_jump_to": 0, "eventu": [6, 7], "everi": 2, "everyth": 7, "ex": 0, "exact": [0, 7], "exactli": 7, "exampl": [0, 4, 5, 7, 10], "except": 0, "excess": 7, "execut": 11, "exist": 7, "expect": [6, 7], "experi": [5, 7], "experiment": [5, 7], "expon": 0, "export": 0, "ext": 6, "extra": 10, "f": [0, 5, 6, 7], "f0": 6, "f_cut": [0, 5, 7], "facilit": 11, "factor": 10, "fail": 7, "fals": [0, 7], "far": 7, "favorit": 11, "favourit": [5, 10], "featur": [7, 11], "few": [0, 7], "fewer": 8, "ff9900": 5, "fig": 5, "figsiz": [4, 7], "figur": [4, 7], "file": [0, 1, 7, 8, 9, 10, 11], "fill": [0, 4, 7], "filter": [0, 9, 10], "filtertyp": 5, "final": [0, 5, 7], "find": 0, "finish": [0, 7], "first": [0, 5, 6, 7, 8, 11], "fit": 5, "fix": 2, "flag": [0, 2], "float": [0, 4, 7], "follow": [0, 5, 6, 11], "food": 7, "footwork": 6, "forc": 6, "force_triggera": 6, "forg": 0, "forger_kwarg": 0, "form": [0, 2, 4], "format": 7, "frac": 5, "freq": [0, 4, 5, 6, 7], "frequenc": [0, 5, 6, 7, 10], "frequent": 11, "from": [0, 1, 5, 6, 7, 8, 9, 10, 11], "full": [0, 6], "fulli": 0, "fullseq": 8, "func": [0, 5], "function": [0, 2, 4, 6, 7, 9, 10], "funlist": 0, "furthermor": 7, "g": [0, 5, 7], "gain": [0, 5], "gaussian": [0, 7], "gaussian1": [0, 7], "gaussian2": [0, 7], "gaussian_smooth_cutoff": 0, "gener": [0, 4, 6, 7, 10], "get": [0, 5, 7, 8, 10], "getarrai": 0, "getsiscalingandprefix": 0, "gimm": 10, "git": 11, "github": [2, 7, 11], "give": [5, 7], "given": [0, 5], "go": [4, 7], "go_to": [0, 6], "goal": 7, "goe": 7, "goto": 0, "graph": 7, "great": 2, "grei": 8, "gt": [6, 7], "guid": 11, "h": 0, "h_": 5, "h_n": 5, "ha": [0, 5, 6, 7, 8], "had": 10, "halfwai": [4, 7], "han": 5, "hand": [5, 7], "handl": 0, "happen": 0, "harddriv": 6, "have": [0, 2, 4, 5, 6, 7, 10, 11], "heavi": [5, 8], "heavili": 0, "height": [0, 8], "help": [5, 7], "henc": 11, "here": [4, 7, 8, 11], "high": [0, 5, 6, 7], "high_level": 6, "highest": 7, "highli": 11, "hight": 0, "hold": 0, "hole": [4, 7], "hook": 2, "horizont": 7, "hostedtoolcach": [6, 7], "how": [4, 6, 7], "hp": [0, 5, 7], "http": 7, "hz": [0, 5, 6], "i": [0, 2, 4, 5, 6, 7, 8, 10, 11], "id": 11, "ident": [0, 2], "ignor": 0, "imagin": 8, "imaginari": 0, "implement": 7, "import": [4, 5, 6, 7, 8], "improv": 3, "includ": [0, 1, 2, 7, 11], "includetim": 0, "inconsist": 0, "increas": 0, "increment": 0, "inde": 0, "index": [0, 6, 7, 10], "infinit": 0, "info": 0, "inform": [0, 6, 7], "infrastructur": 2, "init_from_json": [0, 4], "initialis": 7, "inlin": 7, "input": [0, 5, 6, 7], "insert": [0, 7], "insertseg": [0, 4, 6, 7, 8], "insid": 7, "inspect": [5, 7], "inst0": [6, 7], "instal": [7, 10], "instanc": [0, 7], "instead": [0, 5, 8], "instr": [6, 7], "instruct": [7, 11], "instrument": [0, 6], "instrument_driv": [6, 7], "int": [0, 4, 5, 7], "integ": [0, 7], "integr": 11, "intend": 0, "intern": [0, 6], "interv": 0, "introduct": [9, 10], "invalid": [2, 7], "invalidforgedsequenceerror": 0, "invers": [0, 5], "invert": [0, 5], "invit": 5, "ipython": 11, "item": 0, "iter": [0, 7], "its": [0, 4, 5, 7, 10], "itself": [0, 11], "januari": 1, "job": 2, "json": [0, 1, 9, 10], "jump": 0, "jump_input": 0, "jump_target": 0, "jupyt": 11, "just": [0, 7, 11], "kei": [0, 6], "keyerror": [0, 6], "keyword": 7, "kill": 0, "kind": 0, "know": 6, "kwarg": 0, "l": 0, "l_n": 5, "label": 5, "lambda": [0, 7], "larg": 8, "last": [0, 6, 7], "later": 6, "launch": 11, "least": 7, "left": [5, 11], "legend": [5, 7], "len": 6, "length": [0, 7], "length_seg": [0, 7], "length_sequenceel": 0, "less": 0, "let": [5, 7, 8, 10], "level": [6, 7], "lib": [6, 7], "librari": 11, "licens": 1, "like": [0, 8], "line": [0, 5, 6, 7, 8, 11], "linearli": 0, "liner": 6, "link": 2, "linspac": [5, 6], "list": [0, 7], "littl": 10, "loadseqxfil": 6, "logic": 10, "long": 8, "longer": 7, "look": [6, 7], "low": [0, 5, 7, 8], "lowest": 0, "lp": [0, 5], "lt": 7, "m": [0, 10], "m1": 0, "m2": 0, "m3": 0, "made": [0, 1, 5], "mai": [0, 5, 7, 11], "mainseq": 8, "make": [0, 4, 5, 7, 8, 11], "make_": 0, "make_awg_fil": 0, "make_send_and_load_awg_fil": 7, "makelinearlyvaryingsequ": 0, "makemeanfit": 7, "makeseqxfil": [0, 6], "makevaryingsequ": [0, 7], "manag": 11, "mandatori": 0, "mani": [0, 2], "manipul": 10, "mark": 2, "marked_for_delet": 0, "marker": [0, 4, 6], "marker1": [0, 7], "marker2": [0, 7], "markerid": [0, 4, 7], "match": [0, 6, 7], "mathcal": 5, "mathrm": 5, "matplotlib": [4, 5, 6, 7, 8], "max": [0, 5, 8], "md": 2, "mean": [0, 5, 6, 7], "measur": 5, "memori": [6, 8], "mention": 11, "method": [0, 2, 7, 11], "mhz": 7, "middl": 0, "mimick": 7, "min": [0, 8], "miniconda": 11, "minimum": [10, 11], "minmax": 0, "missingfrequencieserror": 0, "mix": 11, "mode": [0, 5, 7], "model": 5, "modern": 2, "modifi": 0, "modul": [4, 6, 7, 9, 10], "modulenotfounderror": 6, "monoton": 0, "more": [0, 6, 7, 8, 11], "most": 6, "move": 2, "mpl": [4, 7], "mu": 0, "much": 8, "mulitpli": 5, "must": [0, 4, 6, 7], "my": 10, "myfunc": 7, "mysin": [4, 7], "n": [0, 4, 5, 7, 11], "name": [0, 4, 5, 6, 7, 8, 10, 11], "nameerror": 6, "namelist": 0, "navig": 11, "nb": [6, 7], "nbagg": 4, "ndarrai": 0, "necessari": 0, "need": [0, 4, 5, 6, 11], "neg": 0, "neither": 0, "nest": 0, "never": 7, "new": [0, 3, 7, 11], "new_valu": 7, "newdur": 0, "newpackag": 7, "newseq": 7, "next": [0, 4, 6, 7, 11], "nice": 5, "nois": 5, "non": 0, "none": [0, 4, 7], "nor": 0, "normal": 0, "note": [0, 4, 5, 7, 11], "notebook": [5, 6, 8, 11], "noth": 7, "now": [2, 6, 7, 10], "np": [0, 5, 6, 7], "npoint": 0, "npt": [0, 5], "nrep": 0, "nth": 5, "number": [0, 7, 8], "numpi": [0, 1, 4, 5, 6, 7], "obj_to_plot": 0, "object": [0, 6, 7], "occur": 7, "off": [0, 4, 5, 6, 7], "offici": 11, "offset": [0, 7], "ok": 7, "old": 11, "omit": 7, "onc": [7, 11], "one": [0, 5, 6, 7, 10], "onli": [0, 4, 7, 8], "open": 11, "opt": [6, 7], "option": [0, 7, 8, 11], "order": [0, 5, 7], "origin": [0, 5], "other": [7, 11], "otherwis": 0, "ouput": 0, "our": 5, "out": [0, 8, 10], "output": [0, 2, 8], "outputforawgfil": [0, 7], "outputforseqxfil": [0, 6], "outputforseqxfilewithflag": [0, 2], "over": [4, 5, 7], "overal": 7, "overlap": 7, "overview": 7, "overwrit": 0, "overwritten": [0, 7], "own": 10, "p": 0, "packag": [1, 2, 6, 7, 10, 11], "page": 10, "pain": 0, "paramet": [0, 5], "part": [0, 4, 6, 7], "particular": 10, "pass": [0, 5, 7], "path": [0, 4, 11], "path_to_fil": 0, "peak": [0, 4, 6, 7], "pep516": 2, "perform": [5, 7, 10], "period": [5, 8, 11], "perturb": 8, "phase": [0, 4, 7], "physic": [0, 7], "pi": [5, 7], "pin": 2, "pip": [2, 11], "pkg": 0, "place": 0, "plai": [0, 8, 11], "plain": 11, "plateau": 7, "pleas": [0, 7, 11], "plot": [2, 4, 5, 6, 7, 8, 10], "plotawgoutput": 2, "plotel": 2, "plotsequ": 2, "plotter": [0, 4, 6, 7, 8], "plt": 5, "po": [0, 4, 7], "point": [0, 6, 7, 8], "posit": [0, 4, 7], "poss": [0, 7], "possibl": 5, "possibli": 0, "pre": [0, 5], "precommit": 2, "prefer": 11, "prefix": 0, "prepend": 0, "present": 11, "pretend": 5, "pretti": 0, "previou": [0, 7], "previous": 0, "principl": 0, "print": [0, 4, 7], "prior": 0, "proce": [4, 7], "produc": 11, "prompt": 11, "properti": 0, "protect": 7, "provid": [0, 5, 7], "publish": 11, "pull": 11, "puls": [0, 7, 8, 10], "pulseatom": [0, 4, 6, 7, 8], "pulsebp": 7, "pulsebuild": [4, 9, 10], "pulsepi": 7, "put": [7, 8], "py": [6, 7], "pypi": 2, "pyplot": 5, "pyproject": 2, "pytest": 11, "python": [0, 2, 6, 7, 11], "python3": [6, 7], "qcode": [0, 6, 7, 11], "question": 0, "quit": 11, "rais": [0, 6, 7], "ramp": [0, 4, 6, 7, 8, 10], "ramp2": 7, "ramp_tim": 6, "randn": 5, "random": 5, "rang": [0, 5, 7, 8], "rate": [0, 4, 5, 6, 7], "rather": 0, "rc": [0, 9, 10], "rcparam": [4, 7], "reach": 7, "read": [0, 1, 8, 9, 10], "readback": 4, "readblueprint": 4, "readm": 2, "readout": 8, "real": [0, 5, 7], "realist": 5, "receiv": 0, "recent": 6, "recommend": 11, "recov": 5, "red": 7, "reduc": [8, 10], "regular": 2, "regularli": 11, "reinspect": 7, "reinstal": 11, "rel": [0, 7], "releas": [1, 2], "remov": [0, 2, 7], "removeseg": [0, 7], "removesegmentmark": 0, "repeat": [0, 4, 7, 8], "repeatandvarysequ": [0, 7], "repetit": 0, "replac": [0, 2, 7], "replaced_bi": 0, "replaceeverywher": [0, 7], "repositori": 11, "repres": [0, 7], "requir": [0, 2, 7, 10], "resid": 7, "resolut": 6, "respect": 7, "respons": 7, "rest": 7, "result": [5, 8], "retriev": 7, "return": [0, 4, 5, 7], "right": 5, "ripasso": [9, 10], "round": 0, "run": [0, 11], "s_": 5, "sa": [0, 7], "sai": [7, 8, 10], "said": 0, "same": [0, 5, 7, 8, 10, 11], "sampl": [0, 4, 5, 6, 7], "sample_r": 6, "sane": 5, "saniti": [5, 7], "scale": 0, "scene": 3, "screen": 11, "sdist": 2, "search": 10, "second": [0, 6, 7, 11], "section": [7, 11], "see": [5, 7, 11], "segment": [0, 4], "segmentdurationerror": 0, "segmentmarker1": 0, "segmentmarker2": 0, "select": 11, "self": [6, 11], "semi": 5, "sendseqxfil": 6, "sens": 7, "sensibl": 7, "seq": [0, 4, 6, 7, 8], "seq1": [4, 7], "seq2": [7, 8], "seq3": 8, "seq_dict": 0, "seqamp": 4, "seqlen": 6, "seqnam": [0, 6], "seqoffset": 4, "seqtmp": 4, "sequenc": [1, 2, 8, 9, 10], "sequence_from_descript": 0, "sequencecompatibilityerror": 0, "sequenceconsistencyerror": 0, "sequencingerror": 0, "sequens": 0, "seqx": 0, "seqx_input": 6, "seqx_output": 6, "seri": 6, "set": [0, 2, 4, 6, 7, 8, 10], "set_titl": 5, "set_xlabel": 5, "set_ylabel": 5, "setchannelamplitud": [0, 4, 6, 7], "setchanneldelai": [0, 7], "setchannelfiltercompens": [0, 7], "setchanneloffset": [0, 4, 7], "setchannelvoltagerang": 0, "setsegmentmark": [0, 4, 6, 7], "setsequenceset": [0, 7], "setsequencetrack": 6, "setsequencingeventinput": 0, "setsequencingeventjumptarget": [0, 4, 7], "setsequencinggoto": [0, 4, 6, 7], "setsequencingnumberofrepetit": [0, 4, 7, 8], "setsequencingtriggerwait": [0, 4, 6, 7], "setsequencingxxx": 7, "setsr": [0, 4, 6, 7, 8], "settl": 7, "setup": 2, "sever": [0, 7], "shape": 0, "shell": 11, "short": 0, "should": [0, 5, 7, 11], "show": [7, 8], "showprint": [0, 7], "shut": 6, "sig": 5, "sig_point": 5, "sigma": 0, "signal": [0, 5, 7], "signal1": 5, "signal1_check1": 5, "signal1_check2": 5, "signal1_comp": 5, "signal1_filt": 5, "signal2": 5, "signal2_check1": 5, "signal2_check2": 5, "signal2_comp": 5, "signal2_filt": 5, "signal_compens": 5, "signal_filt": 5, "signatur": [0, 4, 6, 7], "similarli": 5, "simpl": 0, "simpli": 5, "simultan": 0, "sin": [0, 5], "sinc": [6, 7], "sine": [0, 4, 6, 7, 8, 10], "sine_amp": 6, "sine_freq": 6, "sinebp": 7, "singl": [0, 7, 11], "sit": [0, 7], "site": [6, 7], "situat": 5, "size": [0, 8], "slice": 7, "sliceabl": [0, 7], "slope": 7, "small": [4, 7], "smaller": 8, "smooth": [0, 5], "so": [0, 4, 6, 7, 10, 11], "some": [0, 5, 7, 8, 11], "someth": [0, 7], "sometim": 5, "sourc": 0, "space": 5, "spec": 0, "special": 0, "specif": [0, 2, 7, 10], "specifi": [0, 5, 6, 7], "specificationinconsistencyerror": 0, "spyder": 11, "squar": [5, 7, 10], "squarewav": 5, "sr": [0, 4, 5, 6, 7, 8], "stabl": 11, "start": [0, 4, 7, 8, 10], "state": [0, 6], "static": 0, "step": [0, 7], "sthe": 7, "stick": 7, "still": [0, 7, 8], "stop": [0, 4, 6, 7, 8], "str": [0, 4, 7], "straighforward": 7, "string": [0, 7], "structur": 0, "submodul": [2, 10], "subplot": [4, 5, 7], "subsequ": [0, 9, 10], "suppli": [9, 10], "support": [0, 1, 2], "sure": [6, 11], "switch": 7, "switch_on_tim": 7, "system": [5, 8], "t": [0, 5, 7], "t1": [6, 8], "t2": [6, 8], "t3": 8, "t_sine": 6, "tab": 11, "take": [0, 7, 11], "taken": 5, "target": 0, "tau": [0, 5, 7], "tcpip0": [6, 7], "tektronix": [2, 6], "tektronix_awg5014": 7, "tempseq": 4, "termin": 11, "test": [2, 11], "testdata": 4, "text": 5, "tf_amp": [0, 5], "tf_freq": [0, 5], "tf_noise_amp": 5, "tf_point": 5, "than": [0, 7], "thei": [0, 7], "them": [0, 2, 5, 11], "therefor": [0, 10], "thi": [0, 1, 2, 5, 6, 7, 8, 11], "thing": [5, 7, 10], "though": 0, "through": [0, 4, 7, 11], "throughout": 7, "thu": [7, 8], "tight_layout": 5, "time": [0, 5, 6, 7, 8], "timeout": 7, "timestep": 7, "toc": 7, "togeth": [0, 7, 8], "toggl": 0, "toml": 2, "too": [0, 7], "tool": 10, "top": [4, 7], "top2": 7, "total": [0, 7], "traceback": 6, "track": [6, 11], "transfer": [0, 6, 9, 10], "transform": [0, 5], "translat": 7, "transmiss": [5, 7], "travi": 2, "trig_wait": 0, "triga": [0, 6], "trigb": [0, 6], "trigger": [0, 6], "true": [0, 4, 5, 7], "tupl": [0, 6, 7], "turn": [0, 6, 7], "tutori": [6, 9, 10], "tutorial_sequ": 6, "twice": [4, 7, 10], "two": [0, 5, 6, 7], "txt": 2, "type": [0, 5], "u": 5, "un": 5, "uncompres": 8, "undesir": 0, "uninstal": 11, "union": 0, "uniqu": 7, "unit": [0, 7], "unless": 0, "unphys": 5, "unsign": 7, "unsurprisingli": 7, "until": 7, "up": [4, 6, 7], "updat": [2, 6], "upgrad": 11, "upload": [0, 2, 7], "upon": 11, "us": [0, 2, 6, 7, 10], "user": [0, 7, 9, 10, 11], "userwarn": 7, "usual": 7, "v": [0, 6], "v3": 11, "valid": [0, 4, 7], "validatedur": [0, 7], "valu": [0, 5, 7], "valueerror": [0, 7], "vari": [0, 6, 8], "variat": [0, 8], "varym": 7, "verbos": 0, "veri": [5, 7, 8], "version": [1, 2, 7, 11], "vertic": 7, "via": [0, 7, 8, 11], "visualis": 5, "vocabulari": 7, "volt": 7, "voltag": [0, 6, 7, 8], "vscode": 11, "wa": [0, 5, 7], "wai": [6, 7], "wait": [0, 6, 7, 8, 10], "waituntil": [0, 7], "want": [0, 6, 8, 11], "warn": 7, "wave": [0, 5, 10], "waveform": [0, 6, 7, 8], "we": [0, 4, 5, 6, 7, 8, 11], "welcom": 10, "well": [0, 7], "went": 0, "wether": 0, "wfm": [0, 6], "what": [0, 6], "whatev": 0, "wheel": 2, "when": [0, 5, 7, 8, 11], "where": [0, 7, 8], "wherea": 7, "whether": [0, 11], "which": [0, 7, 11], "wide": 8, "window": [2, 11], "without": 7, "work": [4, 7, 11], "wrap": [4, 7], "wrapper": 7, "write": [0, 1, 9, 10], "write_to_json": [0, 4], "writeblueprint": 4, "writen": 0, "wrong": [0, 7], "x64": [6, 7], "yet": 7, "yield": 7, "you": [0, 7, 11], "yourself": 11, "zero": [0, 5, 7]}, "titles": ["broadbean package", "Changelog for broadbean 0.10.0", "Changelog for broadbean 0.11.0", "Changelogs", "Read and Write from JSON file Tutorial", "Filter compensation with the ripasso module", "Introduction", "Pulsebuilding tutorial", "Subsequences", "Broadbean Examples", "Documentation", "Getting Started"], "titleterms": {"0": [1, 2], "1": [6, 8], "10": 1, "11": 2, "2": 6, "3": 6, "4": 6, "5": 6, "5014": 7, "The": 2, "ar": 4, "assign": 6, "awg": 7, "back": 4, "basic": 7, "behind": 2, "blueprint": [0, 4, 7], "break": [1, 2], "broadbean": [0, 1, 2, 9, 11], "build": [4, 6], "chang": [1, 2], "changelog": [1, 2, 3], "compens": [5, 7], "compress": 8, "content": [0, 7], "creat": 11, "date": 11, "delai": 7, "develop": 11, "document": 10, "driver": 6, "ekstra": 4, "element": [0, 4, 7], "environ": 11, "exampl": [8, 9], "file": [4, 6], "filter": [5, 7], "from": 4, "function": 5, "get": 11, "ident": 4, "improv": 2, "indic": 10, "initialis": 6, "instal": 11, "intro": 7, "introduct": 6, "json": 4, "keep": 11, "latest": 11, "lingo": 7, "load": 6, "make": 6, "marker": 7, "modifi": 7, "modul": [0, 5], "new": [1, 2], "normal": 7, "now": 8, "output": [6, 7], "packag": 0, "paramet": 7, "plai": 6, "plot": 0, "pulsebuild": 7, "rc": 5, "read": 4, "releas": 11, "requir": 11, "ripasso": [0, 5], "scene": 2, "segment": 7, "send": 6, "sequenc": [0, 4, 6, 7], "seqx": 6, "set": 11, "special": 7, "start": 11, "step": 6, "submodul": 0, "subsequ": 8, "suppli": 5, "tabl": [7, 10], "tektronix": 7, "test": 4, "tool": 0, "transfer": 5, "tutori": [4, 7], "uncompress": 8, "up": 11, "updat": 11, "us": [8, 11], "user": 5, "vari": 7, "write": 4, "your": 11}}) \ No newline at end of file diff --git a/start/index.html b/start/index.html new file mode 100644 index 000000000..ea5ae9346 --- /dev/null +++ b/start/index.html @@ -0,0 +1,461 @@ + + + + + + + + + Getting Started - broadbean 0.15.0.dev697 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Getting Started

+
+
+
+

Requirements

+

You need a working python 3.7 installation, as the minimum Python version (at present, +we recommend python 3.10), to be able to use broadbean. We highly recommend installing +Miniconda, which takes care of installing Python and managing packages. In the following +it will be assumed that you use Miniconda. Download and install it from here. +Make sure to download the latest version with python 3.10.

+

Once you download, install Miniconda according to the instructions on screen, +choosing the single user installation option.

+

The next section will guide you through the installation of broadbean on Windows.

+
+
+

Installation

+

Before you install broadbean you have to decide whether you want to install the +latest stable published release or if you want to get the latest developer version +from broadbean repository on Azure Devops. To install the official package you will need to +configure your computer

+
+

Create a broadbean environment

+

As mentioned above, it is recommended to use broadbean from a conda environment. +We create that by executing the following command:

+
conda create -n broadbean-env python=3.10
+
+
+

This will create a python 3.9 environment named broadbean-env. +Once the environment is created close your shell and open it again to ensure that changes take effect.

+
+
+

Installing the latest broadbean release

+

The broadbean package can be installed using pip:

+
conda activate broadbean-env
+pip install broadbean
+
+
+
+
+

Setting up your development environment

+

The default broadbean installation does not include packages such as pytest that are required for testing and development. +For development and testing, install broadbean with the test feature. +Note that broadbean requires python v3.7, so be sure to include that option when creating your new development environment. +If you run broadbean from Jupyter notebooks, you may also need to install jupyter into the development environment.

+
conda create -n broadbean-development python=3.10
+conda activate broadbean-development
+cd <path to broadbean repository>
+pip install -e .[test]
+
+
+
+
+

Updating Broadbean

+

If you have installed broadbean with pip, run the following to update:

+
pip install --upgrade broadbean
+
+
+

Updates to broadbean are quite frequent, so please check regularly.

+

If you have installed broadbean from the cloned git repository, pull the broadbean +repository using your favorite method (git bash, git shell, github desktop, …).

+
+
+

Keeping your environment up to date

+

Dependencies are periodically been adjusted for broadbean (and for qcodes, which broadbean is built upon) +and new versions of packages that broadbean depends on get released. +Conda/Miniconda itself is also being updated.

+

Hence, to keep the broadbean conda environment up to date, please run for the activated broadbean environment:

+
conda update -n base conda -c defaults
+conda env update
+
+
+

The first line ensures that the conda package manager it self is +up to date, and the second line will ensure that the latest versions of the +packages used by broadbean are installed. See +here for more +documentation on conda env update.

+

If you are using broadbean from an editable install, you should also reinstall broadbean +before upgrading the environment to make sure that dependencies are tracked correctly using:

+
pip uninstall broadbean
+pip install -e <path-to-repository>
+
+
+

Note that if you install packages yourself into the same +environment it is preferable to install them using conda. There is a chance that +mixing packages from conda and pip will produce a broken environment, +especially if the same package is installed using both pip and conda.

+
+
+
+

Using broadbean

+

For using broadbean, as with any other python library, it is useful to use an +application that facilitates the editing and execution of python files. Some +options are:

+
+
    +
  • Jupyter, a browser based notebook

  • +
  • Vscode, IDE with ipython capabilities

  • +
  • Spyder, an integrated development environment

  • +
+
+

For other options you can launch a terminal either via the Anaconda Navigator +by selecting broadbean in the Environments tab and left-clicking on the play +button or by entering

+
activate broadbean
+
+
+

in the Anaconda prompt.

+

From the terminal you can then start any other application, such as IPython or +just plain old Python.

+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file