diff --git a/grabcut/anaconda-project-lock.yml b/grabcut/anaconda-project-lock.yml
new file mode 100644
index 000000000..0c2574919
--- /dev/null
+++ b/grabcut/anaconda-project-lock.yml
@@ -0,0 +1,1112 @@
+# This is an Anaconda project lock file.
+# The lock file locks down exact versions of all your dependencies.
+#
+# In most cases, this file is automatically maintained by the `anaconda-project` command or GUI tools.
+# It's best to keep this file in revision control (such as git or svn).
+# The file is in YAML format, please see http://www.yaml.org/start.html for more.
+#
+
+#
+# Set to false to ignore locked versions.
+#
+locking_enabled: true
+
+#
+# A key goes in here for each env spec.
+#
+env_specs:
+ test:
+ locked: true
+ env_spec_hash: f1caae9b04ed1dc38e8919fb587b1f471e98aab1
+ platforms:
+ - linux-64
+ - osx-64
+ - win-64
+ packages:
+ all:
+ - argon2-cffi=21.3.0=pyhd8ed1ab_0
+ - atomicwrites=1.4.0=pyh9f0ad1d_0
+ - attrs=21.4.0=pyhd8ed1ab_0
+ - backcall=0.2.0=pyh9f0ad1d_0
+ - backports.functools_lru_cache=1.6.4=pyhd8ed1ab_0
+ - backports=1.0=py_2
+ - beautifulsoup4=4.10.0=pyha770c72_0
+ - bleach=4.1.0=pyhd8ed1ab_0
+ - charset-normalizer=2.0.10=pyhd8ed1ab_0
+ - cloudpickle=2.0.0=pyhd8ed1ab_0
+ - colorama=0.4.4=pyh9f0ad1d_0
+ - colorcet=3.0.0=py_0
+ - cycler=0.11.0=pyhd8ed1ab_0
+ - dask-core=2022.1.0=pyhd8ed1ab_0
+ - dask=2022.1.0=pyhd8ed1ab_0
+ - datashader=0.13.0=py_0
+ - datashape=0.5.4=py_1
+ - decorator=5.1.1=pyhd8ed1ab_0
+ - defusedxml=0.7.1=pyhd8ed1ab_0
+ - entrypoints=0.3=pyhd8ed1ab_1003
+ - flit-core=3.6.0=pyhd8ed1ab_0
+ - fsspec=2022.1.0=pyhd8ed1ab_0
+ - geopandas-base=0.10.2=pyha770c72_1
+ - geoviews-core=1.9.3a3=py_0
+ - geoviews=1.9.3a3=py_0
+ - heapdict=1.0.1=py_0
+ - holoviews=1.14.8a1=py_0
+ - idna=3.3=pyhd8ed1ab_0
+ - importlib_metadata=4.10.1=hd8ed1ab_0
+ - importlib_resources=5.4.0=pyhd8ed1ab_0
+ - ipython_genutils=0.2.0=py_1
+ - ipywidgets=7.6.5=pyhd8ed1ab_0
+ - jinja2=3.0.3=pyhd8ed1ab_0
+ - jsonschema=4.4.0=pyhd8ed1ab_0
+ - jupyter_client=6.1.12=pyhd8ed1ab_0
+ - jupyter_console=6.4.0=pyhd8ed1ab_1
+ - jupyterlab_widgets=1.0.2=pyhd8ed1ab_0
+ - locket=0.2.0=py_2
+ - markdown=3.3.6=pyhd8ed1ab_0
+ - matplotlib-inline=0.1.3=pyhd8ed1ab_0
+ - more-itertools=8.12.0=pyhd8ed1ab_0
+ - multipledispatch=0.6.0=py_0
+ - munkres=1.1.4=pyh9f0ad1d_0
+ - nbconvert=5.6.1=pyhd8ed1ab_2
+ - nbformat=5.1.3=pyhd8ed1ab_0
+ - nbsmoke=0.2.8=py_0
+ - nest-asyncio=1.5.4=pyhd8ed1ab_0
+ - notebook=6.4.8=pyha770c72_0
+ - olefile=0.46=pyh9f0ad1d_1
+ - packaging=21.3=pyhd8ed1ab_0
+ - pandocfilters=1.5.0=pyhd8ed1ab_0
+ - panel=0.13.0a32=py_0
+ - param=1.12.1a1=py_0
+ - parso=0.8.3=pyhd8ed1ab_0
+ - partd=1.2.0=pyhd8ed1ab_0
+ - pickleshare=0.7.5=py_1003
+ - pip=21.3.1=pyhd8ed1ab_0
+ - prometheus_client=0.13.0=pyhd8ed1ab_0
+ - prompt-toolkit=3.0.24=pyha770c72_0
+ - prompt_toolkit=3.0.24=hd8ed1ab_0
+ - py=1.11.0=pyh6c4a22f_0
+ - pycparser=2.21=pyhd8ed1ab_0
+ - pyct-core=0.4.8=py_0
+ - pyct=0.4.8=py_0
+ - pyflakes=2.4.0=pyhd8ed1ab_0
+ - pygments=2.11.2=pyhd8ed1ab_0
+ - pyopenssl=21.0.0=pyhd8ed1ab_0
+ - pyparsing=3.0.7=pyhd8ed1ab_0
+ - pyshp=2.1.3=pyh44b312d_0
+ - pytest=4.4.1=py37_0
+ - python-dateutil=2.8.2=pyhd8ed1ab_0
+ - python_abi=3.7=2_cp37m
+ - pytz=2021.3=pyhd8ed1ab_0
+ - pyviz_comms=2.1.0=py_0
+ - qtconsole-base=5.2.2=pyhd8ed1ab_1
+ - qtconsole=5.2.2=pyhd8ed1ab_1
+ - qtpy=2.0.0=pyhd8ed1ab_0
+ - requests=2.27.1=pyhd8ed1ab_0
+ - send2trash=1.8.0=pyhd8ed1ab_0
+ - six=1.16.0=pyh6c4a22f_0
+ - sortedcontainers=2.4.0=pyhd8ed1ab_0
+ - soupsieve=2.3.1=pyhd8ed1ab_0
+ - tblib=1.7.0=pyhd8ed1ab_0
+ - testpath=0.5.0=pyhd8ed1ab_0
+ - toolz=0.11.2=pyhd8ed1ab_0
+ - tqdm=4.62.3=pyhd8ed1ab_0
+ - traitlets=5.1.1=pyhd8ed1ab_0
+ - typing_extensions=4.0.1=pyha770c72_0
+ - urllib3=1.26.8=pyhd8ed1ab_1
+ - wcwidth=0.2.5=pyh9f0ad1d_2
+ - webencodings=0.5.1=py_1
+ - wheel=0.37.1=pyhd8ed1ab_0
+ - xarray=0.20.2=pyhd8ed1ab_0
+ - zict=2.0.0=py_0
+ - zipp=3.7.0=pyhd8ed1ab_0
+ unix:
+ - font-ttf-dejavu-sans-mono=2.37=hab24e00_0
+ - font-ttf-inconsolata=3.000=h77eed37_0
+ - font-ttf-source-code-pro=2.038=h77eed37_0
+ - font-ttf-ubuntu=0.83=hab24e00_0
+ - fonts-conda-ecosystem=1=0
+ - fonts-conda-forge=1=0
+ - pexpect=4.8.0=pyh9f0ad1d_2
+ - ptyprocess=0.7.0=pyhd3deb0d_0
+ linux-64:
+ - _libgcc_mutex=0.1=conda_forge
+ - _openmp_mutex=4.5=1_gnu
+ - alsa-lib=1.2.3=h516909a_0
+ - aom=3.2.0=h9c3ff4c_2
+ - argon2-cffi-bindings=21.2.0=py37h5e8e339_1
+ - bokeh=2.4.2=py37h89c1867_0
+ - brotli-bin=1.0.9=h7f98852_6
+ - brotli=1.0.9=h7f98852_6
+ - brotlipy=0.7.0=py37h5e8e339_1003
+ - bzip2=1.0.8=h7f98852_4
+ - c-ares=1.18.1=h7f98852_0
+ - ca-certificates=2021.10.8=ha878542_0
+ - cairo=1.16.0=ha00ac49_1009
+ - cartopy=0.19.0.post1=py37h0c48da3_1
+ - certifi=2021.10.8=py37h89c1867_1
+ - cffi=1.15.0=py37h036bc23_0
+ - cftime=1.5.2=py37hb1e94ed_0
+ - click=8.0.3=py37h89c1867_1
+ - cryptography=36.0.1=py37hf1a17b8_0
+ - curl=7.81.0=h2574ce0_0
+ - cytoolz=0.11.2=py37h5e8e339_1
+ - dbus=1.13.6=h5008d03_3
+ - debugpy=1.5.1=py37hcd2ae1e_0
+ - distributed=2022.1.0=py37h89c1867_0
+ - expat=2.4.3=h9c3ff4c_0
+ - ffmpeg=4.4.1=h6987444_0
+ - fontconfig=2.13.94=ha180cfb_0
+ - fonttools=4.29.0=py37h5e8e339_0
+ - freeglut=3.2.1=h9c3ff4c_2
+ - freetype=2.10.4=h0708190_1
+ - geos=3.9.1=h9c3ff4c_2
+ - gettext=0.19.8.1=h73d1719_1008
+ - gmp=6.2.1=h58526e2_0
+ - gnutls=3.6.13=h85f3911_1
+ - graphite2=1.3.13=h58526e2_1001
+ - gst-plugins-base=1.18.5=hf529b03_3
+ - gstreamer=1.18.5=h9f60fe5_3
+ - harfbuzz=3.2.0=hb4a5f5f_0
+ - hdf4=4.2.15=h10796ff_3
+ - hdf5=1.12.1=nompi_h2750804_103
+ - icu=69.1=h9c3ff4c_0
+ - importlib-metadata=4.10.1=py37h89c1867_0
+ - ipykernel=6.7.0=py37h6531663_0
+ - ipython=7.31.1=py37h89c1867_0
+ - jasper=2.0.33=ha77e612_0
+ - jbig=2.1=h7f98852_2003
+ - jedi=0.18.1=py37h89c1867_0
+ - jpeg=9e=h7f98852_0
+ - jupyter=1.0.0=py37h89c1867_7
+ - jupyter_core=4.9.1=py37h89c1867_1
+ - kiwisolver=1.3.2=py37h2527ec5_1
+ - krb5=1.19.2=hcc1bbae_3
+ - lame=3.100=h7f98852_1001
+ - lcms2=2.12=hddcbb42_0
+ - ld_impl_linux-64=2.36.1=hea4e1c9_2
+ - lerc=3.0=h9c3ff4c_0
+ - libblas=3.9.0=13_linux64_openblas
+ - libbrotlicommon=1.0.9=h7f98852_6
+ - libbrotlidec=1.0.9=h7f98852_6
+ - libbrotlienc=1.0.9=h7f98852_6
+ - libcblas=3.9.0=13_linux64_openblas
+ - libclang=13.0.0=default_hc23dcda_0
+ - libcurl=7.81.0=h2574ce0_0
+ - libdeflate=1.8=h7f98852_0
+ - libdrm=2.4.109=h7f98852_0
+ - libedit=3.1.20191231=he28a2e2_2
+ - libev=4.33=h516909a_1
+ - libevent=2.1.10=h9b69904_4
+ - libffi=3.4.2=h7f98852_5
+ - libgcc-ng=11.2.0=h1d223b6_12
+ - libgfortran-ng=11.2.0=h69a702a_12
+ - libgfortran5=11.2.0=h5c6108e_12
+ - libglib=2.70.2=h174f98d_1
+ - libglu=9.0.0=he1b5a44_1001
+ - libgomp=11.2.0=h1d223b6_12
+ - libiconv=1.16=h516909a_0
+ - liblapack=3.9.0=13_linux64_openblas
+ - liblapacke=3.9.0=13_linux64_openblas
+ - libllvm11=11.1.0=hf817b99_2
+ - libllvm13=13.0.0=hf817b99_0
+ - libnetcdf=4.8.1=nompi_hb3fd0d9_101
+ - libnghttp2=1.46.0=h812cca2_0
+ - libnsl=2.0.0=h7f98852_0
+ - libogg=1.3.4=h7f98852_1
+ - libopenblas=0.3.18=pthreads_h8fe5266_0
+ - libopencv=4.5.5=py37h4958a1a_0
+ - libopus=1.3.1=h7f98852_1
+ - libpciaccess=0.16=h516909a_0
+ - libpng=1.6.37=h21135ba_2
+ - libpq=14.1=hd57d9b9_1
+ - libprotobuf=3.19.3=h780b84a_0
+ - libsodium=1.0.18=h36c2ea0_1
+ - libssh2=1.10.0=ha56f1ee_2
+ - libstdcxx-ng=11.2.0=he4da1e4_12
+ - libtiff=4.3.0=h6f004c6_2
+ - libuuid=2.32.1=h7f98852_1000
+ - libva=2.13.0=h7f98852_2
+ - libvorbis=1.3.7=h9c3ff4c_0
+ - libvpx=1.11.0=h9c3ff4c_3
+ - libwebp-base=1.2.2=h7f98852_1
+ - libxcb=1.13=h7f98852_1004
+ - libxkbcommon=1.0.3=he3ba5ed_0
+ - libxml2=2.9.12=h885dcf4_1
+ - libzip=1.8.0=h4de3113_1
+ - libzlib=1.2.11=h36c2ea0_1013
+ - llvmlite=0.38.0=py37h9d7f4d0_0
+ - lz4-c=1.9.3=h9c3ff4c_1
+ - markupsafe=2.0.1=py37h5e8e339_1
+ - matplotlib-base=3.5.1=py37h1058ff1_0
+ - matplotlib=3.5.1=py37h89c1867_0
+ - mistune=0.8.4=py37h5e8e339_1005
+ - msgpack-python=1.0.3=py37h2527ec5_0
+ - mysql-common=8.0.28=ha770c72_0
+ - mysql-libs=8.0.28=hfa10184_0
+ - ncurses=6.3=h9c3ff4c_0
+ - netcdf4=1.5.8=nompi_py37hf784469_101
+ - nettle=3.6=he412f7d_0
+ - nspr=4.32=h9c3ff4c_1
+ - nss=3.74=hb5efdd6_0
+ - numba=0.55.0=py37h2d894fd_0
+ - numpy=1.21.5=py37hf2998dd_0
+ - opencv=4.5.5=py37h89c1867_0
+ - openh264=2.1.1=h780b84a_0
+ - openjpeg=2.4.0=hb52868f_1
+ - openssl=1.1.1l=h7f98852_0
+ - pandas=1.3.5=py37he8f5f7f_0
+ - pcre=8.45=h9c3ff4c_0
+ - pillow=8.4.0=py37h0f21c89_0
+ - pixman=0.40.0=h36c2ea0_0
+ - pluggy=1.0.0=py37h89c1867_2
+ - proj=7.2.0=h277dcde_2
+ - psutil=5.9.0=py37h5e8e339_0
+ - pthread-stubs=0.4=h36c2ea0_1001
+ - py-opencv=4.5.5=py37h6531663_0
+ - pyproj=3.1.0=py37h20b8899_3
+ - pyqt-impl=5.12.3=py37hac37412_8
+ - pyqt5-sip=4.19.18=py37hcd2ae1e_8
+ - pyqt=5.12.3=py37h89c1867_8
+ - pyqtchart=5.12=py37he336c9b_8
+ - pyqtwebengine=5.12.1=py37he336c9b_8
+ - pyrsistent=0.18.1=py37h5e8e339_0
+ - pysocks=1.7.1=py37h89c1867_4
+ - python=3.7.12=hb7a2778_100_cpython
+ - pyyaml=6.0=py37h5e8e339_3
+ - pyzmq=22.3.0=py37h336d617_1
+ - qt=5.12.9=ha98a1a1_5
+ - readline=8.1=h46c0cb4_0
+ - scipy=1.7.3=py37hf2a6cf1_0
+ - setuptools=59.8.0=py37h89c1867_0
+ - shapely=1.7.1=py37h48c49eb_5
+ - sqlite=3.37.0=h9cd32fc_0
+ - svt-av1=0.9.0=h9c3ff4c_0
+ - terminado=0.12.1=py37h89c1867_1
+ - tk=8.6.11=h27826a3_1
+ - tornado=6.1=py37h5e8e339_2
+ - unicodedata2=14.0.0=py37h5e8e339_0
+ - widgetsnbextension=3.5.2=py37h89c1867_1
+ - x264=1!161.3030=h7f98852_1
+ - x265=3.5=h4bd325d_1
+ - xorg-fixesproto=5.0=h7f98852_1002
+ - xorg-inputproto=2.3.2=h7f98852_1002
+ - xorg-kbproto=1.0.7=h7f98852_1002
+ - xorg-libice=1.0.10=h7f98852_0
+ - xorg-libsm=1.2.3=hd9c2040_1000
+ - xorg-libx11=1.7.2=h7f98852_0
+ - xorg-libxau=1.0.9=h7f98852_0
+ - xorg-libxdmcp=1.1.3=h7f98852_0
+ - xorg-libxext=1.3.4=h7f98852_1
+ - xorg-libxfixes=5.0.3=h7f98852_1004
+ - xorg-libxi=1.7.10=h7f98852_0
+ - xorg-libxrender=0.9.10=h7f98852_1003
+ - xorg-renderproto=0.11.1=h7f98852_1002
+ - xorg-xextproto=7.3.0=h7f98852_1002
+ - xorg-xproto=7.0.31=h7f98852_1007
+ - xz=5.2.5=h516909a_1
+ - yaml=0.2.5=h7f98852_2
+ - zeromq=4.3.4=h9c3ff4c_1
+ - zlib=1.2.11=h36c2ea0_1013
+ - zstd=1.5.2=ha95c52a_0
+ osx-64:
+ - appnope=0.1.2=py37hf985489_2
+ - argon2-cffi-bindings=21.2.0=py37h271585c_1
+ - bokeh=2.4.2=py37hf985489_0
+ - brotli-bin=1.0.9=h0d85af4_6
+ - brotli=1.0.9=h0d85af4_6
+ - brotlipy=0.7.0=py37h271585c_1003
+ - bzip2=1.0.8=h0d85af4_4
+ - c-ares=1.18.1=h0d85af4_0
+ - ca-certificates=2021.10.8=h033912b_0
+ - cairo=1.16.0=he01c77b_1009
+ - cartopy=0.19.0.post1=py37h77ab93d_1
+ - certifi=2021.10.8=py37hf985489_1
+ - cffi=1.15.0=py37h446072c_0
+ - cftime=1.5.2=py37h032687b_0
+ - click=8.0.3=py37hf985489_1
+ - cryptography=36.0.1=py37h5e77fcc_0
+ - curl=7.81.0=hf45b732_0
+ - cytoolz=0.11.2=py37h271585c_1
+ - dbus=1.13.6=h811a1a6_3
+ - debugpy=1.5.1=py37hd8d24ac_0
+ - distributed=2022.1.0=py37hf985489_0
+ - expat=2.4.3=he49afe7_0
+ - ffmpeg=4.3.2=h4dad6da_1
+ - fontconfig=2.13.94=h10f422b_0
+ - fonttools=4.29.0=py37h271585c_0
+ - freetype=2.10.4=h4cff582_1
+ - geos=3.9.1=he49afe7_2
+ - gettext=0.19.8.1=hd1a6beb_1008
+ - gmp=6.2.1=h2e338ed_0
+ - gnutls=3.6.13=h756fd2b_1
+ - graphite2=1.3.13=h2e338ed_1001
+ - harfbuzz=3.2.0=h447b35c_0
+ - hdf4=4.2.15=hefd3b78_3
+ - hdf5=1.12.1=nompi_h2f0ef1a_103
+ - icu=69.1=he49afe7_0
+ - importlib-metadata=4.10.1=py37hf985489_0
+ - ipykernel=6.7.0=py37h4c52d7d_0
+ - ipython=7.31.1=py37hf985489_0
+ - jasper=2.0.33=h013e400_0
+ - jbig=2.1=h0d85af4_2003
+ - jedi=0.18.1=py37hf985489_0
+ - jpeg=9e=h0d85af4_0
+ - jupyter=1.0.0=py37hf985489_7
+ - jupyter_core=4.9.1=py37hf985489_1
+ - kiwisolver=1.3.2=py37h737db71_1
+ - krb5=1.19.2=hcfbf3a7_3
+ - lame=3.100=h35c211d_1001
+ - lcms2=2.12=h577c468_0
+ - lerc=3.0=he49afe7_0
+ - libblas=3.9.0=13_osx64_openblas
+ - libbrotlicommon=1.0.9=h0d85af4_6
+ - libbrotlidec=1.0.9=h0d85af4_6
+ - libbrotlienc=1.0.9=h0d85af4_6
+ - libcblas=3.9.0=13_osx64_openblas
+ - libclang=13.0.0=default_he082bbe_0
+ - libcurl=7.81.0=hf45b732_0
+ - libcxx=12.0.1=habf9029_1
+ - libdeflate=1.8=h0d85af4_0
+ - libedit=3.1.20191231=h0678c8f_2
+ - libev=4.33=haf1e3a3_1
+ - libffi=3.4.2=h0d85af4_5
+ - libgfortran5=9.3.0=h6c81a4c_23
+ - libgfortran=5.0.0=9_3_0_h6c81a4c_23
+ - libglib=2.70.2=hf1fb8c0_1
+ - libiconv=1.16=haf1e3a3_0
+ - liblapack=3.9.0=13_osx64_openblas
+ - liblapacke=3.9.0=13_osx64_openblas
+ - libllvm11=11.1.0=hd011deb_2
+ - libllvm13=13.0.0=hd011deb_0
+ - libnetcdf=4.8.1=nompi_h6609ca0_101
+ - libnghttp2=1.46.0=h6f36284_0
+ - libopenblas=0.3.18=openmp_h3351f45_0
+ - libopencv=4.5.5=py37hc7bab3b_0
+ - libpng=1.6.37=h7cec526_2
+ - libpq=14.1=hea3049e_1
+ - libprotobuf=3.19.3=hcf210ce_0
+ - libsodium=1.0.18=hbcb3906_1
+ - libssh2=1.10.0=h52ee1ee_2
+ - libtiff=4.3.0=hd146c10_2
+ - libwebp-base=1.2.2=h0d85af4_1
+ - libxml2=2.9.12=h7e28ab6_1
+ - libzip=1.8.0=h8b0c345_1
+ - libzlib=1.2.11=h9173be1_1013
+ - llvm-openmp=12.0.1=hda6cdc1_1
+ - llvmlite=0.38.0=py37h62ea057_0
+ - lz4-c=1.9.3=he49afe7_1
+ - markupsafe=2.0.1=py37h271585c_1
+ - matplotlib-base=3.5.1=py37h3147e9e_0
+ - matplotlib=3.5.1=py37hf985489_0
+ - mistune=0.8.4=py37h271585c_1005
+ - msgpack-python=1.0.3=py37h737db71_0
+ - mysql-common=8.0.28=h694c41f_0
+ - mysql-libs=8.0.28=h115446f_0
+ - ncurses=6.3=he49afe7_0
+ - netcdf4=1.5.8=nompi_py37h5a78667_101
+ - nettle=3.6=hedd7734_0
+ - nspr=4.32=hcd9eead_1
+ - nss=3.74=h31e2bf1_0
+ - numba=0.55.0=py37h078fc1e_0
+ - numpy=1.21.5=py37h3c8089f_0
+ - opencv=4.5.5=py37hf985489_0
+ - openh264=2.1.1=hfd3ada9_0
+ - openjpeg=2.4.0=h6e7aa92_1
+ - openssl=1.1.1l=h0d85af4_0
+ - pandas=1.3.5=py37h5b83a90_0
+ - pcre=8.45=he49afe7_0
+ - pillow=8.4.0=py37h76dc067_0
+ - pixman=0.40.0=hbcb3906_0
+ - pluggy=1.0.0=py37hf985489_2
+ - proj=7.2.0=h78d1473_2
+ - psutil=5.9.0=py37h271585c_0
+ - py-opencv=4.5.5=py37h4c52d7d_0
+ - pyproj=3.1.0=py37hf99dfa2_3
+ - pyqt-impl=5.12.3=py37hab5ec1f_8
+ - pyqt5-sip=4.19.18=py37h070e122_8
+ - pyqt=5.12.3=py37hf985489_8
+ - pyqtchart=5.12=py37hab5ec1f_8
+ - pyqtwebengine=5.12.1=py37hab5ec1f_8
+ - pyrsistent=0.18.1=py37h271585c_0
+ - pysocks=1.7.1=py37hf985489_4
+ - python=3.7.12=haf480d7_100_cpython
+ - pyyaml=6.0=py37h271585c_3
+ - pyzmq=22.3.0=py37h8f778e5_1
+ - qt=5.12.9=h2a607e2_5
+ - readline=8.1=h05e3726_0
+ - scipy=1.7.3=py37h4e3cf02_0
+ - setuptools=59.8.0=py37hf985489_0
+ - shapely=1.7.1=py37hf313787_5
+ - sqlite=3.37.0=h23a322b_0
+ - terminado=0.12.1=py37hf985489_1
+ - tk=8.6.11=h5dbffcc_1
+ - tornado=6.1=py37h271585c_2
+ - unicodedata2=14.0.0=py37h271585c_0
+ - widgetsnbextension=3.5.2=py37hf985489_1
+ - x264=1!161.3030=h0d85af4_1
+ - xz=5.2.5=haf1e3a3_1
+ - yaml=0.2.5=h0d85af4_2
+ - zeromq=4.3.4=he49afe7_1
+ - zlib=1.2.11=h9173be1_1013
+ - zstd=1.5.2=h582d3a0_0
+ win-64:
+ - argon2-cffi-bindings=21.2.0=py37hcc03f2d_1
+ - bokeh=2.4.2=py37h03978a9_0
+ - brotli-bin=1.0.9=h8ffe710_6
+ - brotli=1.0.9=h8ffe710_6
+ - brotlipy=0.7.0=py37hcc03f2d_1003
+ - bzip2=1.0.8=h8ffe710_4
+ - ca-certificates=2021.10.8=h5b45459_0
+ - cartopy=0.19.0.post1=py37h24ea9ba_1
+ - certifi=2021.10.8=py37h03978a9_1
+ - cffi=1.15.0=py37hd8e9650_0
+ - cftime=1.5.2=py37hec80d1f_0
+ - click=8.0.3=py37h03978a9_1
+ - cryptography=36.0.1=py37h65266a2_0
+ - curl=7.81.0=h789b8ee_0
+ - cytoolz=0.11.2=py37hcc03f2d_1
+ - debugpy=1.5.1=py37hf2a7229_0
+ - distributed=2022.1.0=py37h03978a9_0
+ - fonttools=4.29.0=py37hcc03f2d_0
+ - freeglut=3.2.1=h0e60522_2
+ - freetype=2.10.4=h546665d_1
+ - geos=3.9.1=h39d44d4_2
+ - hdf4=4.2.15=h0e5069d_3
+ - hdf5=1.12.1=nompi_h2a0e4a3_103
+ - icu=68.2=h0e60522_0
+ - importlib-metadata=4.10.1=py37h03978a9_0
+ - intel-openmp=2022.0.0=h57928b3_3663
+ - ipykernel=6.7.0=py37h4038f58_0
+ - ipython=7.31.1=py37h03978a9_0
+ - jasper=2.0.33=h77af90b_0
+ - jbig=2.1=h8d14728_2003
+ - jedi=0.18.1=py37h03978a9_0
+ - jpeg=9e=h8ffe710_0
+ - jupyter=1.0.0=py37h03978a9_7
+ - jupyter_core=4.9.1=py37h03978a9_1
+ - kiwisolver=1.3.2=py37h8c56517_1
+ - krb5=1.19.2=h20d022d_3
+ - lcms2=2.12=h2a16943_0
+ - lerc=3.0=h0e60522_0
+ - libblas=3.9.0=13_win64_mkl
+ - libbrotlicommon=1.0.9=h8ffe710_6
+ - libbrotlidec=1.0.9=h8ffe710_6
+ - libbrotlienc=1.0.9=h8ffe710_6
+ - libcblas=3.9.0=13_win64_mkl
+ - libclang=11.1.0=default_h5c34c98_1
+ - libcurl=7.81.0=h789b8ee_0
+ - libdeflate=1.8=h8ffe710_0
+ - liblapack=3.9.0=13_win64_mkl
+ - liblapacke=3.9.0=13_win64_mkl
+ - libnetcdf=4.8.1=nompi_h1cc8e9d_101
+ - libopencv=4.5.5=py37h04cf790_0
+ - libpng=1.6.37=h1d00b33_2
+ - libprotobuf=3.19.3=h7755175_0
+ - libsodium=1.0.18=h8d14728_1
+ - libssh2=1.10.0=h680486a_2
+ - libtiff=4.3.0=hd413186_2
+ - libwebp-base=1.2.2=h8ffe710_1
+ - libzip=1.8.0=hfed4ece_1
+ - libzlib=1.2.11=h8ffe710_1013
+ - llvmlite=0.38.0=py37habb0c8c_0
+ - lz4-c=1.9.3=h8ffe710_1
+ - m2w64-gcc-libgfortran=5.3.0=6
+ - m2w64-gcc-libs-core=5.3.0=7
+ - m2w64-gcc-libs=5.3.0=7
+ - m2w64-gmp=6.1.0=2
+ - m2w64-libwinpthread-git=5.0.0.4634.697f757=2
+ - markupsafe=2.0.1=py37hcc03f2d_1
+ - matplotlib-base=3.5.1=py37h4a79c79_0
+ - matplotlib=3.5.1=py37h03978a9_0
+ - mistune=0.8.4=py37hcc03f2d_1005
+ - mkl=2022.0.0=h0e2418a_796
+ - msgpack-python=1.0.3=py37h8c56517_0
+ - msys2-conda-epoch=20160418=1
+ - netcdf4=1.5.8=nompi_py37h8860187_101
+ - numba=0.55.0=py37h3e99911_0
+ - numpy=1.21.5=py37h5fa1a60_0
+ - opencv=4.5.5=py37h03978a9_0
+ - openjpeg=2.4.0=hb211442_1
+ - openssl=1.1.1l=h8ffe710_0
+ - pandas=1.3.5=py37h9386db6_0
+ - pillow=8.4.0=py37hd7d9ad0_0
+ - pluggy=1.0.0=py37h03978a9_2
+ - proj=7.2.0=h1cfcee9_2
+ - psutil=5.9.0=py37hcc03f2d_0
+ - py-opencv=4.5.5=py37h4038f58_0
+ - pyproj=3.1.0=py37h895d2b2_3
+ - pyqt-impl=5.12.3=py37hf2a7229_8
+ - pyqt5-sip=4.19.18=py37hf2a7229_8
+ - pyqt=5.12.3=py37h03978a9_8
+ - pyqtchart=5.12=py37hf2a7229_8
+ - pyqtwebengine=5.12.1=py37hf2a7229_8
+ - pyrsistent=0.18.1=py37hcc03f2d_0
+ - pysocks=1.7.1=py37h03978a9_4
+ - python=3.7.12=h7840368_100_cpython
+ - pywin32=303=py37hcc03f2d_0
+ - pywinpty=2.0.1=py37h7f67f24_0
+ - pyyaml=6.0=py37hcc03f2d_3
+ - pyzmq=22.3.0=py37hcce574b_1
+ - qt=5.12.9=h5909a2a_4
+ - scipy=1.7.3=py37hb6553fb_0
+ - setuptools=59.8.0=py37h03978a9_0
+ - shapely=1.7.1=py37hc520ffa_5
+ - sqlite=3.37.0=h8ffe710_0
+ - tbb=2021.5.0=h2d74725_0
+ - terminado=0.12.1=py37h03978a9_1
+ - tk=8.6.11=h8ffe710_1
+ - tornado=6.1=py37hcc03f2d_2
+ - ucrt=10.0.20348.0=h57928b3_0
+ - unicodedata2=14.0.0=py37hcc03f2d_0
+ - vc=14.2=hb210afc_6
+ - vs2015_runtime=14.29.30037=h902a5da_6
+ - widgetsnbextension=3.5.2=py37h03978a9_1
+ - win_inet_pton=1.1.0=py37h03978a9_3
+ - winpty=0.4.3=4
+ - xz=5.2.5=h62dcd97_1
+ - yaml=0.2.5=h8ffe710_2
+ - zeromq=4.3.4=h0e60522_1
+ - zlib=1.2.11=h8ffe710_1013
+ - zstd=1.5.2=h6255e5f_0
+ default:
+ locked: true
+ env_spec_hash: 9f119a78d608ba412234a59e51f2ece0da9e7eca
+ platforms:
+ - linux-64
+ - osx-64
+ - win-64
+ packages:
+ all:
+ - argon2-cffi=21.3.0=pyhd8ed1ab_0
+ - attrs=21.4.0=pyhd8ed1ab_0
+ - backcall=0.2.0=pyh9f0ad1d_0
+ - backports.functools_lru_cache=1.6.4=pyhd8ed1ab_0
+ - backports=1.0=py_2
+ - bleach=4.1.0=pyhd8ed1ab_0
+ - charset-normalizer=2.0.10=pyhd8ed1ab_0
+ - cloudpickle=2.0.0=pyhd8ed1ab_0
+ - colorama=0.4.4=pyh9f0ad1d_0
+ - colorcet=3.0.0=py_0
+ - cycler=0.11.0=pyhd8ed1ab_0
+ - dask-core=2022.1.0=pyhd8ed1ab_0
+ - dask=2022.1.0=pyhd8ed1ab_0
+ - datashader=0.13.0=py_0
+ - datashape=0.5.4=py_1
+ - decorator=5.1.1=pyhd8ed1ab_0
+ - defusedxml=0.7.1=pyhd8ed1ab_0
+ - entrypoints=0.3=pyhd8ed1ab_1003
+ - flit-core=3.6.0=pyhd8ed1ab_0
+ - fsspec=2022.1.0=pyhd8ed1ab_0
+ - geopandas-base=0.10.2=pyha770c72_1
+ - geoviews-core=1.9.3a3=py_0
+ - geoviews=1.9.3a3=py_0
+ - heapdict=1.0.1=py_0
+ - holoviews=1.14.8a1=py_0
+ - idna=3.3=pyhd8ed1ab_0
+ - importlib_metadata=4.10.1=hd8ed1ab_0
+ - importlib_resources=5.4.0=pyhd8ed1ab_0
+ - ipython_genutils=0.2.0=py_1
+ - ipywidgets=7.6.5=pyhd8ed1ab_0
+ - jinja2=3.0.3=pyhd8ed1ab_0
+ - jsonschema=4.4.0=pyhd8ed1ab_0
+ - jupyter_client=6.1.12=pyhd8ed1ab_0
+ - jupyter_console=6.4.0=pyhd8ed1ab_1
+ - jupyterlab_widgets=1.0.2=pyhd8ed1ab_0
+ - locket=0.2.0=py_2
+ - markdown=3.3.6=pyhd8ed1ab_0
+ - matplotlib-inline=0.1.3=pyhd8ed1ab_0
+ - multipledispatch=0.6.0=py_0
+ - munkres=1.1.4=pyh9f0ad1d_0
+ - nbconvert=5.6.1=pyhd8ed1ab_2
+ - nbformat=5.1.3=pyhd8ed1ab_0
+ - nest-asyncio=1.5.4=pyhd8ed1ab_0
+ - notebook=6.4.8=pyha770c72_0
+ - olefile=0.46=pyh9f0ad1d_1
+ - packaging=21.3=pyhd8ed1ab_0
+ - pandocfilters=1.5.0=pyhd8ed1ab_0
+ - panel=0.13.0a32=py_0
+ - param=1.12.1a1=py_0
+ - parso=0.8.3=pyhd8ed1ab_0
+ - partd=1.2.0=pyhd8ed1ab_0
+ - pickleshare=0.7.5=py_1003
+ - pip=21.3.1=pyhd8ed1ab_0
+ - prometheus_client=0.13.0=pyhd8ed1ab_0
+ - prompt-toolkit=3.0.24=pyha770c72_0
+ - prompt_toolkit=3.0.24=hd8ed1ab_0
+ - pycparser=2.21=pyhd8ed1ab_0
+ - pyct-core=0.4.8=py_0
+ - pyct=0.4.8=py_0
+ - pygments=2.11.2=pyhd8ed1ab_0
+ - pyopenssl=21.0.0=pyhd8ed1ab_0
+ - pyparsing=3.0.7=pyhd8ed1ab_0
+ - pyshp=2.1.3=pyh44b312d_0
+ - python-dateutil=2.8.2=pyhd8ed1ab_0
+ - python_abi=3.7=2_cp37m
+ - pytz=2021.3=pyhd8ed1ab_0
+ - pyviz_comms=2.1.0=py_0
+ - qtconsole-base=5.2.2=pyhd8ed1ab_1
+ - qtconsole=5.2.2=pyhd8ed1ab_1
+ - qtpy=2.0.0=pyhd8ed1ab_0
+ - requests=2.27.1=pyhd8ed1ab_0
+ - send2trash=1.8.0=pyhd8ed1ab_0
+ - six=1.16.0=pyh6c4a22f_0
+ - sortedcontainers=2.4.0=pyhd8ed1ab_0
+ - tblib=1.7.0=pyhd8ed1ab_0
+ - testpath=0.5.0=pyhd8ed1ab_0
+ - toolz=0.11.2=pyhd8ed1ab_0
+ - tqdm=4.62.3=pyhd8ed1ab_0
+ - traitlets=5.1.1=pyhd8ed1ab_0
+ - typing_extensions=4.0.1=pyha770c72_0
+ - urllib3=1.26.8=pyhd8ed1ab_1
+ - wcwidth=0.2.5=pyh9f0ad1d_2
+ - webencodings=0.5.1=py_1
+ - wheel=0.37.1=pyhd8ed1ab_0
+ - xarray=0.20.2=pyhd8ed1ab_0
+ - zict=2.0.0=py_0
+ - zipp=3.7.0=pyhd8ed1ab_0
+ unix:
+ - font-ttf-dejavu-sans-mono=2.37=hab24e00_0
+ - font-ttf-inconsolata=3.000=h77eed37_0
+ - font-ttf-source-code-pro=2.038=h77eed37_0
+ - font-ttf-ubuntu=0.83=hab24e00_0
+ - fonts-conda-ecosystem=1=0
+ - fonts-conda-forge=1=0
+ - pexpect=4.8.0=pyh9f0ad1d_2
+ - ptyprocess=0.7.0=pyhd3deb0d_0
+ linux-64:
+ - _libgcc_mutex=0.1=conda_forge
+ - _openmp_mutex=4.5=1_gnu
+ - alsa-lib=1.2.3=h516909a_0
+ - aom=3.2.0=h9c3ff4c_2
+ - argon2-cffi-bindings=21.2.0=py37h5e8e339_1
+ - bokeh=2.4.2=py37h89c1867_0
+ - brotli-bin=1.0.9=h7f98852_6
+ - brotli=1.0.9=h7f98852_6
+ - brotlipy=0.7.0=py37h5e8e339_1003
+ - bzip2=1.0.8=h7f98852_4
+ - c-ares=1.18.1=h7f98852_0
+ - ca-certificates=2021.10.8=ha878542_0
+ - cairo=1.16.0=ha00ac49_1009
+ - cartopy=0.19.0.post1=py37h0c48da3_1
+ - certifi=2021.10.8=py37h89c1867_1
+ - cffi=1.15.0=py37h036bc23_0
+ - cftime=1.5.2=py37hb1e94ed_0
+ - click=8.0.3=py37h89c1867_1
+ - cryptography=36.0.1=py37hf1a17b8_0
+ - curl=7.81.0=h2574ce0_0
+ - cytoolz=0.11.2=py37h5e8e339_1
+ - dbus=1.13.6=h5008d03_3
+ - debugpy=1.5.1=py37hcd2ae1e_0
+ - distributed=2022.1.0=py37h89c1867_0
+ - expat=2.4.3=h9c3ff4c_0
+ - ffmpeg=4.4.1=h6987444_0
+ - fontconfig=2.13.94=ha180cfb_0
+ - fonttools=4.29.0=py37h5e8e339_0
+ - freeglut=3.2.1=h9c3ff4c_2
+ - freetype=2.10.4=h0708190_1
+ - geos=3.9.1=h9c3ff4c_2
+ - gettext=0.19.8.1=h73d1719_1008
+ - gmp=6.2.1=h58526e2_0
+ - gnutls=3.6.13=h85f3911_1
+ - graphite2=1.3.13=h58526e2_1001
+ - gst-plugins-base=1.18.5=hf529b03_3
+ - gstreamer=1.18.5=h9f60fe5_3
+ - harfbuzz=3.2.0=hb4a5f5f_0
+ - hdf4=4.2.15=h10796ff_3
+ - hdf5=1.12.1=nompi_h2750804_103
+ - icu=69.1=h9c3ff4c_0
+ - importlib-metadata=4.10.1=py37h89c1867_0
+ - ipykernel=6.7.0=py37h6531663_0
+ - ipython=7.31.1=py37h89c1867_0
+ - jasper=2.0.33=ha77e612_0
+ - jbig=2.1=h7f98852_2003
+ - jedi=0.18.1=py37h89c1867_0
+ - jpeg=9e=h7f98852_0
+ - jupyter=1.0.0=py37h89c1867_7
+ - jupyter_core=4.9.1=py37h89c1867_1
+ - kiwisolver=1.3.2=py37h2527ec5_1
+ - krb5=1.19.2=hcc1bbae_3
+ - lame=3.100=h7f98852_1001
+ - lcms2=2.12=hddcbb42_0
+ - ld_impl_linux-64=2.36.1=hea4e1c9_2
+ - lerc=3.0=h9c3ff4c_0
+ - libblas=3.9.0=13_linux64_openblas
+ - libbrotlicommon=1.0.9=h7f98852_6
+ - libbrotlidec=1.0.9=h7f98852_6
+ - libbrotlienc=1.0.9=h7f98852_6
+ - libcblas=3.9.0=13_linux64_openblas
+ - libclang=13.0.0=default_hc23dcda_0
+ - libcurl=7.81.0=h2574ce0_0
+ - libdeflate=1.8=h7f98852_0
+ - libdrm=2.4.109=h7f98852_0
+ - libedit=3.1.20191231=he28a2e2_2
+ - libev=4.33=h516909a_1
+ - libevent=2.1.10=h9b69904_4
+ - libffi=3.4.2=h7f98852_5
+ - libgcc-ng=11.2.0=h1d223b6_12
+ - libgfortran-ng=11.2.0=h69a702a_12
+ - libgfortran5=11.2.0=h5c6108e_12
+ - libglib=2.70.2=h174f98d_1
+ - libglu=9.0.0=he1b5a44_1001
+ - libgomp=11.2.0=h1d223b6_12
+ - libiconv=1.16=h516909a_0
+ - liblapack=3.9.0=13_linux64_openblas
+ - liblapacke=3.9.0=13_linux64_openblas
+ - libllvm11=11.1.0=hf817b99_2
+ - libllvm13=13.0.0=hf817b99_0
+ - libnetcdf=4.8.1=nompi_hb3fd0d9_101
+ - libnghttp2=1.46.0=h812cca2_0
+ - libnsl=2.0.0=h7f98852_0
+ - libogg=1.3.4=h7f98852_1
+ - libopenblas=0.3.18=pthreads_h8fe5266_0
+ - libopencv=4.5.5=py37h4958a1a_0
+ - libopus=1.3.1=h7f98852_1
+ - libpciaccess=0.16=h516909a_0
+ - libpng=1.6.37=h21135ba_2
+ - libpq=14.1=hd57d9b9_1
+ - libprotobuf=3.19.3=h780b84a_0
+ - libsodium=1.0.18=h36c2ea0_1
+ - libssh2=1.10.0=ha56f1ee_2
+ - libstdcxx-ng=11.2.0=he4da1e4_12
+ - libtiff=4.3.0=h6f004c6_2
+ - libuuid=2.32.1=h7f98852_1000
+ - libva=2.13.0=h7f98852_2
+ - libvorbis=1.3.7=h9c3ff4c_0
+ - libvpx=1.11.0=h9c3ff4c_3
+ - libwebp-base=1.2.2=h7f98852_1
+ - libxcb=1.13=h7f98852_1004
+ - libxkbcommon=1.0.3=he3ba5ed_0
+ - libxml2=2.9.12=h885dcf4_1
+ - libzip=1.8.0=h4de3113_1
+ - libzlib=1.2.11=h36c2ea0_1013
+ - llvmlite=0.38.0=py37h9d7f4d0_0
+ - lz4-c=1.9.3=h9c3ff4c_1
+ - markupsafe=2.0.1=py37h5e8e339_1
+ - matplotlib-base=3.5.1=py37h1058ff1_0
+ - matplotlib=3.5.1=py37h89c1867_0
+ - mistune=0.8.4=py37h5e8e339_1005
+ - msgpack-python=1.0.3=py37h2527ec5_0
+ - mysql-common=8.0.28=ha770c72_0
+ - mysql-libs=8.0.28=hfa10184_0
+ - ncurses=6.3=h9c3ff4c_0
+ - netcdf4=1.5.8=nompi_py37hf784469_101
+ - nettle=3.6=he412f7d_0
+ - nspr=4.32=h9c3ff4c_1
+ - nss=3.74=hb5efdd6_0
+ - numba=0.55.0=py37h2d894fd_0
+ - numpy=1.21.5=py37hf2998dd_0
+ - opencv=4.5.5=py37h89c1867_0
+ - openh264=2.1.1=h780b84a_0
+ - openjpeg=2.4.0=hb52868f_1
+ - openssl=1.1.1l=h7f98852_0
+ - pandas=1.3.5=py37he8f5f7f_0
+ - pcre=8.45=h9c3ff4c_0
+ - pillow=8.4.0=py37h0f21c89_0
+ - pixman=0.40.0=h36c2ea0_0
+ - proj=7.2.0=h277dcde_2
+ - psutil=5.9.0=py37h5e8e339_0
+ - pthread-stubs=0.4=h36c2ea0_1001
+ - py-opencv=4.5.5=py37h6531663_0
+ - pyproj=3.1.0=py37h20b8899_3
+ - pyqt-impl=5.12.3=py37hac37412_8
+ - pyqt5-sip=4.19.18=py37hcd2ae1e_8
+ - pyqt=5.12.3=py37h89c1867_8
+ - pyqtchart=5.12=py37he336c9b_8
+ - pyqtwebengine=5.12.1=py37he336c9b_8
+ - pyrsistent=0.18.1=py37h5e8e339_0
+ - pysocks=1.7.1=py37h89c1867_4
+ - python=3.7.12=hb7a2778_100_cpython
+ - pyyaml=6.0=py37h5e8e339_3
+ - pyzmq=22.3.0=py37h336d617_1
+ - qt=5.12.9=ha98a1a1_5
+ - readline=8.1=h46c0cb4_0
+ - scipy=1.7.3=py37hf2a6cf1_0
+ - setuptools=59.8.0=py37h89c1867_0
+ - shapely=1.7.1=py37h48c49eb_5
+ - sqlite=3.37.0=h9cd32fc_0
+ - svt-av1=0.9.0=h9c3ff4c_0
+ - terminado=0.12.1=py37h89c1867_1
+ - tk=8.6.11=h27826a3_1
+ - tornado=6.1=py37h5e8e339_2
+ - unicodedata2=14.0.0=py37h5e8e339_0
+ - widgetsnbextension=3.5.2=py37h89c1867_1
+ - x264=1!161.3030=h7f98852_1
+ - x265=3.5=h4bd325d_1
+ - xorg-fixesproto=5.0=h7f98852_1002
+ - xorg-inputproto=2.3.2=h7f98852_1002
+ - xorg-kbproto=1.0.7=h7f98852_1002
+ - xorg-libice=1.0.10=h7f98852_0
+ - xorg-libsm=1.2.3=hd9c2040_1000
+ - xorg-libx11=1.7.2=h7f98852_0
+ - xorg-libxau=1.0.9=h7f98852_0
+ - xorg-libxdmcp=1.1.3=h7f98852_0
+ - xorg-libxext=1.3.4=h7f98852_1
+ - xorg-libxfixes=5.0.3=h7f98852_1004
+ - xorg-libxi=1.7.10=h7f98852_0
+ - xorg-libxrender=0.9.10=h7f98852_1003
+ - xorg-renderproto=0.11.1=h7f98852_1002
+ - xorg-xextproto=7.3.0=h7f98852_1002
+ - xorg-xproto=7.0.31=h7f98852_1007
+ - xz=5.2.5=h516909a_1
+ - yaml=0.2.5=h7f98852_2
+ - zeromq=4.3.4=h9c3ff4c_1
+ - zlib=1.2.11=h36c2ea0_1013
+ - zstd=1.5.2=ha95c52a_0
+ osx-64:
+ - appnope=0.1.2=py37hf985489_2
+ - argon2-cffi-bindings=21.2.0=py37h271585c_1
+ - bokeh=2.4.2=py37hf985489_0
+ - brotli-bin=1.0.9=h0d85af4_6
+ - brotli=1.0.9=h0d85af4_6
+ - brotlipy=0.7.0=py37h271585c_1003
+ - bzip2=1.0.8=h0d85af4_4
+ - c-ares=1.18.1=h0d85af4_0
+ - ca-certificates=2021.10.8=h033912b_0
+ - cairo=1.16.0=he01c77b_1009
+ - cartopy=0.18.0=py37hf1ba7ce_1
+ - certifi=2021.10.8=py37hf985489_1
+ - cffi=1.15.0=py37h446072c_0
+ - cftime=1.5.2=py37h032687b_0
+ - click=8.0.3=py37hf985489_1
+ - cryptography=36.0.1=py37h5e77fcc_0
+ - curl=7.81.0=hf45b732_0
+ - cytoolz=0.11.2=py37h271585c_1
+ - dbus=1.13.6=h811a1a6_3
+ - debugpy=1.5.1=py37hd8d24ac_0
+ - distributed=2022.1.0=py37hf985489_0
+ - expat=2.4.3=he49afe7_0
+ - ffmpeg=4.3.2=h4dad6da_1
+ - fontconfig=2.13.94=h10f422b_0
+ - fonttools=4.29.0=py37h271585c_0
+ - freetype=2.10.4=h4cff582_1
+ - geos=3.8.0=hb1e8313_0
+ - gettext=0.19.8.1=hd1a6beb_1008
+ - gmp=6.2.1=h2e338ed_0
+ - gnutls=3.6.13=h756fd2b_1
+ - graphite2=1.3.13=h2e338ed_1001
+ - harfbuzz=3.2.0=h447b35c_0
+ - hdf4=4.2.15=hefd3b78_3
+ - hdf5=1.12.1=nompi_h2f0ef1a_103
+ - icu=69.1=he49afe7_0
+ - importlib-metadata=4.10.1=py37hf985489_0
+ - ipykernel=6.7.0=py37h4c52d7d_0
+ - ipython=7.31.1=py37hf985489_0
+ - jasper=2.0.33=h013e400_0
+ - jbig=2.1=h0d85af4_2003
+ - jedi=0.18.1=py37hf985489_0
+ - jpeg=9e=h0d85af4_0
+ - jupyter=1.0.0=py37hf985489_7
+ - jupyter_core=4.9.1=py37hf985489_1
+ - kiwisolver=1.3.2=py37h737db71_1
+ - krb5=1.19.2=hcfbf3a7_3
+ - lame=3.100=h35c211d_1001
+ - lcms2=2.12=h577c468_0
+ - lerc=3.0=he49afe7_0
+ - libblas=3.9.0=13_osx64_openblas
+ - libbrotlicommon=1.0.9=h0d85af4_6
+ - libbrotlidec=1.0.9=h0d85af4_6
+ - libbrotlienc=1.0.9=h0d85af4_6
+ - libcblas=3.9.0=13_osx64_openblas
+ - libclang=13.0.0=default_he082bbe_0
+ - libcurl=7.81.0=hf45b732_0
+ - libcxx=12.0.1=habf9029_1
+ - libdeflate=1.8=h0d85af4_0
+ - libedit=3.1.20191231=h0678c8f_2
+ - libev=4.33=haf1e3a3_1
+ - libffi=3.4.2=h0d85af4_5
+ - libgfortran5=9.3.0=h6c81a4c_23
+ - libgfortran=5.0.0=9_3_0_h6c81a4c_23
+ - libglib=2.70.2=hf1fb8c0_1
+ - libiconv=1.16=haf1e3a3_0
+ - liblapack=3.9.0=13_osx64_openblas
+ - liblapacke=3.9.0=13_osx64_openblas
+ - libllvm11=11.1.0=hd011deb_2
+ - libllvm13=13.0.0=hd011deb_0
+ - libnetcdf=4.8.1=nompi_h6609ca0_101
+ - libnghttp2=1.46.0=h6f36284_0
+ - libopenblas=0.3.18=openmp_h3351f45_0
+ - libopencv=4.5.5=py37hc7bab3b_0
+ - libpng=1.6.37=h7cec526_2
+ - libpq=14.1=hea3049e_1
+ - libprotobuf=3.19.3=hcf210ce_0
+ - libsodium=1.0.18=hbcb3906_1
+ - libssh2=1.10.0=h52ee1ee_2
+ - libtiff=4.3.0=hd146c10_2
+ - libwebp-base=1.2.2=h0d85af4_1
+ - libxml2=2.9.12=h7e28ab6_1
+ - libzip=1.8.0=h8b0c345_1
+ - libzlib=1.2.11=h9173be1_1013
+ - llvm-openmp=12.0.1=hda6cdc1_1
+ - llvmlite=0.38.0=py37h62ea057_0
+ - lz4-c=1.9.3=he49afe7_1
+ - markupsafe=2.0.1=py37h271585c_1
+ - matplotlib-base=3.5.1=py37h3147e9e_0
+ - matplotlib=3.5.1=py37hf985489_0
+ - mistune=0.8.4=py37h271585c_1005
+ - msgpack-python=1.0.3=py37h737db71_0
+ - mysql-common=8.0.28=h694c41f_0
+ - mysql-libs=8.0.28=h115446f_0
+ - ncurses=6.3=he49afe7_0
+ - netcdf4=1.5.8=nompi_py37h5a78667_101
+ - nettle=3.6=hedd7734_0
+ - nspr=4.32=hcd9eead_1
+ - nss=3.74=h31e2bf1_0
+ - numba=0.55.0=py37h078fc1e_0
+ - numpy=1.21.5=py37h3c8089f_0
+ - opencv=4.5.5=py37hf985489_0
+ - openh264=2.1.1=hfd3ada9_0
+ - openjpeg=2.4.0=h6e7aa92_1
+ - openssl=1.1.1l=h0d85af4_0
+ - pandas=1.3.5=py37h5b83a90_0
+ - pcre=8.45=he49afe7_0
+ - pillow=8.4.0=py37h76dc067_0
+ - pixman=0.40.0=hbcb3906_0
+ - proj=6.2.1=h773a61f_0
+ - psutil=5.9.0=py37h271585c_0
+ - py-opencv=4.5.5=py37h4c52d7d_0
+ - pyproj=2.6.1.post1=py37hdfdfadc_1
+ - pyqt-impl=5.12.3=py37hab5ec1f_8
+ - pyqt5-sip=4.19.18=py37h070e122_8
+ - pyqt=5.12.3=py37hf985489_8
+ - pyqtchart=5.12=py37hab5ec1f_8
+ - pyqtwebengine=5.12.1=py37hab5ec1f_8
+ - pyrsistent=0.18.1=py37h271585c_0
+ - pysocks=1.7.1=py37hf985489_4
+ - python=3.7.12=haf480d7_100_cpython
+ - pyyaml=6.0=py37h271585c_3
+ - pyzmq=22.3.0=py37h8f778e5_1
+ - qt=5.12.9=h2a607e2_5
+ - readline=8.1=h05e3726_0
+ - scipy=1.7.3=py37h4e3cf02_0
+ - setuptools=59.8.0=py37hf985489_0
+ - shapely=1.7.1=py37h9250791_0
+ - sqlite=3.37.0=h23a322b_0
+ - terminado=0.12.1=py37hf985489_1
+ - tk=8.6.11=h5dbffcc_1
+ - tornado=6.1=py37h271585c_2
+ - unicodedata2=14.0.0=py37h271585c_0
+ - widgetsnbextension=3.5.2=py37hf985489_1
+ - x264=1!161.3030=h0d85af4_1
+ - xz=5.2.5=haf1e3a3_1
+ - yaml=0.2.5=h0d85af4_2
+ - zeromq=4.3.4=he49afe7_1
+ - zlib=1.2.11=h9173be1_1013
+ - zstd=1.5.2=h582d3a0_0
+ win-64:
+ - argon2-cffi-bindings=21.2.0=py37hcc03f2d_1
+ - bokeh=2.4.2=py37h03978a9_0
+ - brotli-bin=1.0.9=h8ffe710_6
+ - brotli=1.0.9=h8ffe710_6
+ - brotlipy=0.7.0=py37hcc03f2d_1003
+ - bzip2=1.0.8=h8ffe710_4
+ - ca-certificates=2021.10.8=h5b45459_0
+ - cartopy=0.18.0=py37h80a4efb_1
+ - certifi=2021.10.8=py37h03978a9_1
+ - cffi=1.15.0=py37hd8e9650_0
+ - cftime=1.5.2=py37hec80d1f_0
+ - click=8.0.3=py37h03978a9_1
+ - cryptography=36.0.1=py37h65266a2_0
+ - curl=7.81.0=h789b8ee_0
+ - cytoolz=0.11.2=py37hcc03f2d_1
+ - debugpy=1.5.1=py37hf2a7229_0
+ - distributed=2022.1.0=py37h03978a9_0
+ - fonttools=4.29.0=py37hcc03f2d_0
+ - freeglut=3.2.1=h0e60522_2
+ - freetype=2.10.4=h546665d_1
+ - geos=3.8.0=h33f27b4_0
+ - hdf4=4.2.15=h0e5069d_3
+ - hdf5=1.12.1=nompi_h2a0e4a3_103
+ - icu=68.2=h0e60522_0
+ - importlib-metadata=4.10.1=py37h03978a9_0
+ - intel-openmp=2022.0.0=h57928b3_3663
+ - ipykernel=6.7.0=py37h4038f58_0
+ - ipython=7.31.1=py37h03978a9_0
+ - jasper=2.0.33=h77af90b_0
+ - jbig=2.1=h8d14728_2003
+ - jedi=0.18.1=py37h03978a9_0
+ - jpeg=9e=h8ffe710_0
+ - jupyter=1.0.0=py37h03978a9_7
+ - jupyter_core=4.9.1=py37h03978a9_1
+ - kiwisolver=1.3.2=py37h8c56517_1
+ - krb5=1.19.2=h20d022d_3
+ - lcms2=2.12=h2a16943_0
+ - lerc=3.0=h0e60522_0
+ - libblas=3.9.0=13_win64_mkl
+ - libbrotlicommon=1.0.9=h8ffe710_6
+ - libbrotlidec=1.0.9=h8ffe710_6
+ - libbrotlienc=1.0.9=h8ffe710_6
+ - libcblas=3.9.0=13_win64_mkl
+ - libclang=11.1.0=default_h5c34c98_1
+ - libcurl=7.81.0=h789b8ee_0
+ - libdeflate=1.8=h8ffe710_0
+ - liblapack=3.9.0=13_win64_mkl
+ - liblapacke=3.9.0=13_win64_mkl
+ - libnetcdf=4.8.1=nompi_h1cc8e9d_101
+ - libopencv=4.5.5=py37h04cf790_0
+ - libpng=1.6.37=h1d00b33_2
+ - libprotobuf=3.19.3=h7755175_0
+ - libsodium=1.0.18=h8d14728_1
+ - libssh2=1.10.0=h680486a_2
+ - libtiff=4.3.0=hd413186_2
+ - libwebp-base=1.2.2=h8ffe710_1
+ - libzip=1.8.0=hfed4ece_1
+ - libzlib=1.2.11=h8ffe710_1013
+ - llvmlite=0.38.0=py37habb0c8c_0
+ - lz4-c=1.9.3=h8ffe710_1
+ - m2w64-gcc-libgfortran=5.3.0=6
+ - m2w64-gcc-libs-core=5.3.0=7
+ - m2w64-gcc-libs=5.3.0=7
+ - m2w64-gmp=6.1.0=2
+ - m2w64-libwinpthread-git=5.0.0.4634.697f757=2
+ - markupsafe=2.0.1=py37hcc03f2d_1
+ - matplotlib-base=3.5.1=py37h4a79c79_0
+ - matplotlib=3.5.1=py37h03978a9_0
+ - mistune=0.8.4=py37hcc03f2d_1005
+ - mkl=2022.0.0=h0e2418a_796
+ - msgpack-python=1.0.3=py37h8c56517_0
+ - msys2-conda-epoch=20160418=1
+ - netcdf4=1.5.8=nompi_py37h8860187_101
+ - numba=0.55.0=py37h3e99911_0
+ - numpy=1.21.5=py37h5fa1a60_0
+ - opencv=4.5.5=py37h03978a9_0
+ - openjpeg=2.4.0=hb211442_1
+ - openssl=1.1.1l=h8ffe710_0
+ - pandas=1.3.5=py37h9386db6_0
+ - pillow=8.4.0=py37hd7d9ad0_0
+ - proj=6.2.1=h9f7ef89_0
+ - psutil=5.9.0=py37hcc03f2d_0
+ - py-opencv=4.5.5=py37h4038f58_0
+ - pyproj=2.6.1.post1=py37h593ac45_1
+ - pyqt-impl=5.12.3=py37hf2a7229_8
+ - pyqt5-sip=4.19.18=py37hf2a7229_8
+ - pyqt=5.12.3=py37h03978a9_8
+ - pyqtchart=5.12=py37hf2a7229_8
+ - pyqtwebengine=5.12.1=py37hf2a7229_8
+ - pyrsistent=0.18.1=py37hcc03f2d_0
+ - pysocks=1.7.1=py37h03978a9_4
+ - python=3.7.12=h7840368_100_cpython
+ - pywin32=303=py37hcc03f2d_0
+ - pywinpty=2.0.1=py37h7f67f24_0
+ - pyyaml=6.0=py37hcc03f2d_3
+ - pyzmq=22.3.0=py37hcce574b_1
+ - qt=5.12.9=h5909a2a_4
+ - scipy=1.7.3=py37hb6553fb_0
+ - setuptools=59.8.0=py37h03978a9_0
+ - shapely=1.7.1=py37h06580b3_0
+ - sqlite=3.37.0=h8ffe710_0
+ - tbb=2021.5.0=h2d74725_0
+ - terminado=0.12.1=py37h03978a9_1
+ - tk=8.6.11=h8ffe710_1
+ - tornado=6.1=py37hcc03f2d_2
+ - ucrt=10.0.20348.0=h57928b3_0
+ - unicodedata2=14.0.0=py37hcc03f2d_0
+ - vc=14.2=hb210afc_6
+ - vs2015_runtime=14.29.30037=h902a5da_6
+ - widgetsnbextension=3.5.2=py37h03978a9_1
+ - win_inet_pton=1.1.0=py37h03978a9_3
+ - winpty=0.4.3=4
+ - xz=5.2.5=h62dcd97_1
+ - yaml=0.2.5=h8ffe710_2
+ - zeromq=4.3.4=h0e60522_1
+ - zlib=1.2.11=h8ffe710_1013
+ - zstd=1.5.2=h6255e5f_0
diff --git a/grabcut/anaconda-project.yml b/grabcut/anaconda-project.yml
new file mode 100644
index 000000000..86efa9b81
--- /dev/null
+++ b/grabcut/anaconda-project.yml
@@ -0,0 +1,64 @@
+# To reproduce: install 'anaconda-project', then 'anaconda-project run'
+name: grabcut
+description: Extract coast/shoreline contour with GrabCut
+maintainers:
+- maximlt
+labels:
+- channel_conda-forge
+- geoviews
+- panel
+
+user_fields: [labels, skip, maintainers]
+
+channels:
+- pyviz/label/dev
+- conda-forge
+- nodefaults
+
+packages: &pkgs
+ - cartopy
+ - geoviews
+ - holoviews
+ - panel
+ - datashader
+ - pillow
+ - nbconvert<6.0
+ - jupyter_client<6.2
+ - numpy
+ - pandas
+ - opencv
+ - python>3.7
+ - notebook
+ - shapely
+
+dependencies: *pkgs
+
+commands:
+ notebook:
+ notebook: grabcut.ipynb
+ dashboard:
+ unix: panel serve grabcut.ipynb --show
+ supports_http_options: true
+ test:
+ unix: pytest --nbsmoke-run -k *.ipynb --ignore envs
+ windows: pytest --nbsmoke-run -k *.ipynb --ignore envs
+ env_spec: test
+ lint:
+ unix: pytest --nbsmoke-lint -k *.ipynb --ignore envs
+ windows: pytest --nbsmoke-lint -k *.ipynb --ignore envs
+ env_spec: test
+
+variables: {}
+downloads: {}
+
+env_specs:
+ default: {}
+ test:
+ packages: &testpkgs
+ - nbsmoke=0.2.8
+ - pytest=4.4.1
+ dependencies: *testpkgs
+platforms:
+- linux-64
+- osx-64
+- win-64
diff --git a/grabcut/grabcut.ipynb b/grabcut/grabcut.ipynb
new file mode 100644
index 000000000..ce5a69b2f
--- /dev/null
+++ b/grabcut/grabcut.ipynb
@@ -0,0 +1,149 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Grabcut\n",
+ "\n",
+ "Written by Philipp Rudiger and Maxime Liquet
\n",
+ "Created: November 16, 2018
\n",
+ "Last updated: April 26, 2024"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Extract a coast- and shoreline contour with GrabCut\n",
+ "\n",
+ "The GrabCut algorithm provides a way to annotate an image using polygons or lines to demark the foreground and background. The algorithm estimates the color distribution of the target object and that of the background using a Gaussian mixture model. This is used to construct a Markov random field over the pixel labels, with an energy function that prefers connected regions having the same label, and running a graph-cut-based optimization to infer their values. This procedure is repeated until convergence, resulting in an image mask denoting the foreground and background.\n",
+ "\n",
+ "In this example this algorithm is applied to map tiles to automatically extract a coast or shoreline contour. First we specify a rectangular region in which to download the map tiles using the `SelectRegionPanel`, then we can declare the ``GrabCutPanel`` to annotate the region and let the algorithm compute a contour."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import panel as pn\n",
+ "import holoviews as hv\n",
+ "import geoviews as gv\n",
+ "from grabcut import paths_to_polys, GrabCutPanel, SelectRegionPanel\n",
+ "\n",
+ "gv.extension('bokeh')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "select_region = SelectRegionPanel(hv.Bounds((-77.5, 34.45, -77.3, 34.75)), magnification=1)\n",
+ "pn.Row(select_region.param, select_region.view())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The toolbar in the plot on the left contains two polygon/polyline drawing tools to annotate the image with foreground (in red) and background (in cyan) regions respectively. To demonstrate this process in a static notebook there are already two polygons declared, one marking the land as the foreground and one marking the sea as the background."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "background = np.array([\n",
+ " [-77.3777271 , 34.66037492], [-77.35987035, 34.62251189], [-77.34130751, 34.64016586],\n",
+ " [-77.35563287, 34.65360275], [-77.36083954, 34.66560481], [-77.3777271 , 34.66037492]\n",
+ "])\n",
+ "foreground = np.array([\n",
+ " [-77.46585666, 34.66965009], [-77.46451121, 34.62795592], [-77.43105867, 34.64501054],\n",
+ " [-77.41376085, 34.62573423], [-77.37886112,34.63780581], [-77.41283172, 34.6800562 ],\n",
+ " [-77.46585666, 34.66965009]\n",
+ "])\n",
+ "dashboard = GrabCutPanel(select_region.get_image(), fg_data=[foreground], bg_data=[background], minimum_size=500, tolerance=0.001, width=400)\n",
+ "pn.Row(dashboard.param, dashboard.view())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Pressing the ``Update Contour`` runs the *GrabCut* algorithm on the selected region with the foreground and background polygons/paths given as input. The output of this process is a contour that delineates the foreground and background. This countour is also filtered and simplified to make it smaller to export and easier to edit. Try to set ``Minimum size`` to 1 and ``Tolerance`` to 200 to observe the effects of filtering and simplifying the contour, respectively. To speed up the calculation we can also downsample the image before applying the Grabcut algorithm by reducing the value of ``magnification``.\n",
+ "\n",
+ "Next we can further edit the extracted contour, by attaching a ``PolyEdit`` stream to the contour:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "poly_edit = gv.streams.PolyEdit(source=dashboard.result, vertex_style={'color': 'red'}, shared=True)\n",
+ "(gv.tile_sources.ESRI * dashboard.result).opts(active_tools=['poly_edit'], width=500, height=500)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Once we are done we can convert the paths to a polygon and view the result in a separate cell:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gv.tile_sources.ESRI * paths_to_polys(poly_edit.element).opts(width=500, height=500, color_index=None)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Deploy as a dashboard\n",
+ "\n",
+ "We will allow the first two stages of the workflow introduced above to be deployed with Panel as a pipeline, where the ouput of a stage feeds the input of a next stage. Marking the pipeline with the `.servable()` method allows Panel to know which object to serve when the notebook is executed with the command `panel serve grabcut.ipynb`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "stages = [\n",
+ " ('Select Region', SelectRegionPanel),\n",
+ " ('Grabcut', GrabCutPanel),\n",
+ "]\n",
+ "\n",
+ "pipeline = pn.pipeline.Pipeline(stages)\n",
+ "\n",
+ "pn.Column(\n",
+ " '# Grabcut dashboard',\n",
+ " '1. Download map tiles of your zone of interest\\n2. Draw the background & foreground of the image before running the grabcut algorithm.',\n",
+ " pipeline,\n",
+ ").servable()"
+ ]
+ }
+ ],
+ "metadata": {
+ "language_info": {
+ "name": "python",
+ "pygments_lexer": "ipython3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/grabcut/grabcut.py b/grabcut/grabcut.py
new file mode 100644
index 000000000..c8a00f6ab
--- /dev/null
+++ b/grabcut/grabcut.py
@@ -0,0 +1,473 @@
+"""
+Module that supports the grabcut.ipynb example.
+
+Its content has been extracted and adapted from earthsim.holoviz.org.
+"""
+
+import math
+
+import param
+import panel as pn
+import numpy as np
+import holoviews as hv
+import geoviews as gv
+import cartopy.crs as ccrs
+import datashader as ds
+
+from PIL import Image, ImageDraw
+from geoviews.util import path_to_geom_dicts
+from holoviews.core.operation import Operation
+from holoviews.core.options import Store, Options
+from holoviews.core.spaces import DynamicMap
+from holoviews.core.util import pd
+from holoviews.element.util import split_path
+from holoviews.operation.datashader import ResampleOperation2D, rasterize, regrid
+from holoviews.operation import contours
+from holoviews.streams import FreehandDraw, BoxEdit
+from shapely.geometry import Polygon, LinearRing, MultiPolygon
+
+
+def paths_to_polys(path):
+ """
+ Converts a Path object to a Polygons object by extracting all paths
+ interpreting inclusion zones as holes and then constructing Polygon
+ and MultiPolygon geometries for each path.
+ """
+ geoms = path_to_geom_dicts(path)
+
+ polys = []
+ for geom in geoms:
+ g = geom['geometry']
+ found = False
+ for p in list(polys):
+ if Polygon(p['geometry']).contains(g):
+ if 'holes' not in p:
+ p['holes'] = []
+ p['holes'].append(g)
+ found = True
+ elif Polygon(g).contains(p['geometry']):
+ polys.pop(polys.index(p))
+ if 'holes' not in geom:
+ geom['holes'] = []
+ geom['holes'].append(p['geometry'])
+ if not found:
+ polys.append(geom)
+
+ polys_with_holes = []
+ for p in polys:
+ geom = p['geometry']
+ holes = []
+ if 'holes' in p:
+ holes = [LinearRing(h) for h in p['holes']]
+
+ if 'Multi' in geom.type:
+ polys = []
+ for g in geom:
+ subholes = [h for h in holes if g.intersects(h)]
+ polys.append(Polygon(g, subholes))
+ poly = MultiPolygon(polys)
+ else:
+ poly = Polygon(geom, holes)
+ p['geometry'] = poly
+ polys_with_holes.append(p)
+ return path.clone(polys_with_holes, new_type=gv.Polygons)
+
+
+class rasterize_polygon(ResampleOperation2D):
+ """
+ Rasterizes Polygons elements to a boolean mask using PIL
+ """
+
+ def _process(self, element, key=None):
+ sampling = self._get_sampling(element, 0, 1)
+ (x_range, y_range), (xvals, yvals), (width, height), (xtype, ytype) = sampling
+ (x0, x1), (y0, y1) = x_range, y_range
+ img = Image.new('L', (width, height), 0)
+ draw = ImageDraw.Draw(img)
+ for poly in element.split():
+ poly = poly.reindex(vdims=[])
+ for p in split_path(poly):
+ xs, ys = (p.values if pd else p).T
+ xs = ((xs - x0) / (x1-x0) * width)
+ ys = ((ys - y0) / (y1-y0) * height)
+ draw.polygon(list(zip(xs, ys)), outline=1, fill=1)
+ img = np.array(img).astype('bool')
+ return hv.Image((xvals, yvals, img), element.kdims)
+
+
+class extract_foreground(Operation):
+ """
+ Uses Grabcut algorithm to extract the foreground from an image given
+ path or polygon types.
+ """
+
+ foreground = param.ClassSelector(class_=hv.Path)
+
+ background = param.ClassSelector(class_=hv.Path)
+
+ iterations = param.Integer(default=5, bounds=(0, 20), doc="""
+ Number of iterations to run the GrabCut algorithm for.""")
+
+
+ def _process(self, element, key=None):
+ try:
+ import cv2 as cv
+ except:
+ # HACK: Avoids error loading OpenCV the first time
+ # ImportError dlopen: cannot load any more object with static TLS
+ try:
+ import cv2 as cv
+ except ImportError:
+ raise ImportError('GrabCut algorithm requires openCV')
+
+ if isinstance(self.p.foreground, hv.Polygons):
+ rasterize_op = rasterize_polygon
+ else:
+ rasterize_op = rasterize.instance(aggregator=ds.any())
+
+ kwargs = {'dynamic': False, 'target': element}
+ fg_mask = rasterize_op(self.p.foreground, **kwargs)
+ bg_mask = rasterize_op(self.p.background, **kwargs)
+ fg_mask = fg_mask.dimension_values(2, flat=False)
+ bg_mask = bg_mask.dimension_values(2, flat=False)
+
+ # UPDATE wrt earthsim: Newer versions of holoviews return np.nan instead of 0 (I think)
+ fg_mask = np.nan_to_num(fg_mask)
+ bg_mask = np.nan_to_num(bg_mask)
+
+ if fg_mask[np.isfinite(fg_mask)].sum() == 0 or bg_mask[np.isfinite(bg_mask)].sum() == 0:
+ return element.clone([], vdims=['Foreground'], new_type=gv.Image,
+ crs=element.crs)
+
+ mask = np.where(fg_mask, 1, 2)
+ mask = np.where(bg_mask, 0, mask).copy()
+ bgdModel = np.zeros((1,65), np.float64)
+ fgdModel = np.zeros((1,65), np.float64)
+
+ if isinstance(element, hv.RGB):
+ img = np.dstack([element.dimension_values(d, flat=False)
+ for d in element.vdims])
+ else:
+ img = element.dimension_values(2, flat=False)
+ mask, _, _ = cv.grabCut(img, mask.astype('uint8'), None, bgdModel, fgdModel,
+ self.p.iterations, cv.GC_INIT_WITH_MASK)
+ fg_mask = np.where((mask==2)|(mask==0),0,1).astype('bool')
+ xs, ys = (element.dimension_values(d, expanded=False) for d in element.kdims)
+ return element.clone((xs, ys, fg_mask), vdims=['Foreground'], new_type=gv.Image,
+ crs=element.crs)
+
+class filter_polygons(Operation):
+
+ minimum_size = param.Integer(default=10)
+
+ link_inputs = param.Boolean(default=True)
+
+ def _process(self, element, key=None):
+ paths = []
+ for path in element.split():
+ # UPDATE wrt earthsim: len(path) returns 1 I guess because it's a multipath and that is has only one path
+ # UPDATE wrt earthsim: Is there a better way to get the number of vertices in a path?
+ # if len(path) < self.p.minimum_size:
+ if len(path.dimension_values('Longitude')) < self.p.minimum_size:
+ continue
+ for p in split_path(path):
+ if len(p) > self.p.minimum_size:
+ paths.append(p)
+ return element.clone(paths)
+
+
+class simplify_paths(Operation):
+
+ tolerance = param.Number(default=0.01)
+
+ def _process(self, element, key=None):
+ paths = []
+ for g in path_to_geom_dicts(element):
+ geom = g['geometry']
+ g = dict(g, geometry=geom.simplify(self.p.tolerance))
+ paths.append(g)
+ return element.clone(paths)
+
+
+class GrabCutPanel(param.Parameterized):
+ """
+ Defines a Panel for extracting contours from an Image.
+ """
+
+ crs = param.ClassSelector(default=ccrs.PlateCarree(), class_=ccrs.Projection,
+ precedence=-1, doc="""
+ Projection the inputs and output paths are defined in.""")
+
+ image = param.ClassSelector(class_=gv.RGB, precedence=-1, doc="""
+ The Image to compute contours on""")
+
+ path_type = param.ClassSelector(default=gv.Path, class_=hv.Path,
+ precedence=-1, is_instance=False, doc="""
+ The element type to draw into.""")
+
+ downsample = param.Magnitude(default=1, precedence=1, doc="""
+ Amount to downsample image by before applying grabcut.""")
+
+ iterations = param.Integer(default=5, precedence=1, bounds=(0, 20), doc="""
+ Number of iterations to run the GrabCut algorithm for.""")
+
+ clear = param.Action(default=lambda o: o._trigger_clear(),
+ precedence=2, doc="""
+ Button to clear drawn annotations.""")
+
+ update_contour = param.Action(default=lambda o: o.param.trigger('update_contour'),
+ precedence=2, doc="""
+ Button triggering GrabCut.""")
+
+ minimum_size = param.Integer(default=100, precedence=3)
+
+ filter_contour = param.Action(default=lambda o: o.param.trigger('filter_contour'),
+ precedence=4, doc="""
+ Button triggering filtering of contours.""")
+
+ tolerance = param.Number(default=0.01, precedence=5)
+
+ simplify_contour = param.Action(default=lambda o: o.param.trigger('simplify_contour'),
+ precedence=6, doc="""
+ Simplifies contour.""" )
+
+ width = param.Integer(default=500, precedence=-1, doc="""
+ Width of the plot""")
+
+ height = param.Integer(default=None, precedence=-1, doc="""
+ Height of the plot""")
+
+ def __init__(self, image, fg_data=[], bg_data=[], **params):
+ super(GrabCutPanel, self).__init__(image=image, **params)
+ self._bg_data = bg_data
+ self._fg_data = fg_data
+ self.bg_paths = DynamicMap(self.bg_path_view)
+ self.fg_paths = DynamicMap(self.fg_path_view)
+ self.draw_bg = FreehandDraw(source=self.bg_paths, tooltip="draw_fg")
+ self.draw_fg = FreehandDraw(source=self.fg_paths, tooltip="draw_bg")
+ self._clear = False
+
+ def _trigger_clear(self):
+ self._clear = True
+ self.param.trigger('clear')
+ self._clear = False
+
+ @param.depends('clear')
+ def bg_path_view(self):
+ if self._clear:
+ self._bg_data = []
+ elif self.bg_paths.data:
+ self._bg_data = self.draw_bg.element.data
+ else:
+ self._bg_data = gv.project(self.path_type(self._bg_data, crs=self.crs), projection=self.image.crs)
+ return self.path_type(self._bg_data, crs=self.image.crs).opts(color='cyan')
+
+ @param.depends('clear')
+ def fg_path_view(self):
+ if self._clear:
+ self._fg_data = []
+ elif self.fg_paths.data:
+ self._fg_data = self.draw_fg.element.data
+ else:
+ self._fg_data = gv.project(self.path_type(self._fg_data, crs=self.crs), projection=self.image.crs)
+ return self.path_type(self._fg_data, crs=self.image.crs).opts(color='red')
+
+ @param.depends('update_contour', 'image')
+ def extract_foreground(self, **kwargs):
+ print("UPDATING CONTOURS...")
+ img = self.image
+ bg, fg = self.bg_path_view(), self.fg_path_view()
+
+ if not len(bg) or not len(fg):
+ print("NO SUITABLE FOREGROUND / BACKGROUND FOUND")
+ return gv.Path([], img.kdims, crs=img.crs)
+
+ if self.downsample != 1:
+ kwargs = {'dynamic': False}
+ h, w = img.interface.shape(img, gridded=True)
+ kwargs['width'] = int(w*self.downsample)
+ kwargs['height'] = int(h*self.downsample)
+ img = regrid(img, **kwargs)
+
+ foreground = extract_foreground(img, background=bg, foreground=fg,
+ iterations=self.iterations)
+ # UPDATE wrt earthsim: No need here to wrap the outpout of countours() in a list
+ foreground = gv.Path(contours(foreground, filled=True, levels=1).split()[0].data,
+ kdims=foreground.kdims, crs=foreground.crs)
+ self.result = gv.project(foreground, projection=self.crs)
+ print("FINISHED CONTOURS...")
+ return foreground
+
+ @param.depends('filter_contour')
+ def _filter_contours(self, obj, **kwargs):
+ if self.minimum_size > 0:
+ obj = filter_polygons(obj, minimum_size=self.minimum_size)
+ return obj
+
+ @param.depends('simplify_contour')
+ def _simplify_contours(self, obj, **kwargs):
+ if self.tolerance > 0:
+ obj = simplify_paths(obj, tolerance=self.tolerance)
+ self.result = gv.project(obj, projection=self.crs)
+ return obj
+
+ def view(self):
+ height = self.height
+ if height is None:
+ h, w = self.image.dimension_values(2, flat=False).shape[:2]
+ height = int(self.width*(h/w))
+ options = dict(width=self.width, height=height, xaxis=None, yaxis=None,
+ projection=self.image.crs)
+ dmap = hv.DynamicMap(self.extract_foreground)
+ dmap = hv.util.Dynamic(dmap, operation=self._filter_contours)
+ dmap = hv.util.Dynamic(dmap, operation=self._simplify_contours)
+ return (self.image.opts(**options) * self.bg_paths * self.fg_paths +
+ dmap.opts(**options)).opts(toolbar="left")
+
+ @param.output(polys=hv.Path)
+ def output(self):
+ return self.result
+
+ def panel(self):
+ return pn.Row(self.param, self.view())
+
+
+class SelectRegionPanel(param.Parameterized):
+ """
+ Visualization that allows selecting a bounding box anywhere on a tile source.
+ """
+
+ # Tile servers
+ misc_servers = {'OpenStreetMap': 'http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png',
+ 'Basemaps CartoCDN': 'https://s.basemaps.cartocdn.com/light_all/{Z}/{X}/{Y}.png',
+ 'Stamen': 'http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png'}
+
+ arcgis_paths = {'World Imagery': 'World_Imagery/MapServer/tile/{Z}/{Y}/{X}',
+ 'World Topo Map': 'World_Topo_Map/MapServer/tile/{Z}/{Y}/{X}',
+ 'World Terrain Base': 'World_Terrain_Base/MapServer/tile/{Z}/{Y}/{X}',
+ 'World Street Map': 'World_Street_Map/MapServer/tile/{Z}/{Y}/{X}',
+ 'World Shaded Relief': 'World_Shaded_Relief/MapServer/tile/{Z}/{Y}/{X}',
+ 'World Physical Map': 'World_Physical_Map/MapServer/tile/{Z}/{Y}/{X}',
+ 'USA Topo Maps': 'USA_Topo_Maps/MapServer/tile/{Z}/{Y}/{X}',
+ 'Ocean Basemap': 'Ocean_Basemap/MapServer/tile/{Z}/{Y}/{X}',
+ 'NatGeo World Map': 'NatGeo_World_Map/MapServer/tile/{Z}/{Y}/{X}'}
+
+ arcgis_urls = {k: 'https://server.arcgisonline.com/ArcGIS/rest/services/' + v
+ for k, v in arcgis_paths.items()}
+
+ tile_urls = dict(misc_servers, **arcgis_urls)
+
+ # Parameters
+ name = param.String(default='Region Settings')
+
+ width = param.Integer(default=900, precedence=-1, doc="Width of the plot in pixels")
+
+ height = param.Integer(default=700, precedence=-1, doc="Height of the plot in pixels")
+
+ zoom_level = param.Integer(default=7, bounds=(1,21), precedence=-1, doc="""
+ The zoom level is updated when the bounding box is drawn.""" )
+
+ tile_server = param.ObjectSelector(default=tile_urls['World Imagery'], objects=tile_urls)
+
+ magnification = param.Integer(default=1, bounds=(1,10), precedence=0.1)
+
+ def __init__(self, poly_data=[], **params):
+ super(SelectRegionPanel, self).__init__(**params)
+ self.boxes = gv.Polygons(poly_data).opts(
+ fill_alpha=0.5, color='grey', line_color='white',
+ line_width=2, width=self.width, height=self.height
+ )
+ if not self.boxes:
+ self.boxes = self.boxes.opts(global_extent=True)
+ self.box_stream = BoxEdit(source=self.boxes, num_objects=1)
+
+ @classmethod
+ def bounds_to_zoom_level(cls, bounds, width, height,
+ tile_width=256, tile_height=256, max_zoom=21):
+ """
+ Computes the zoom level from the lat/lon bounds and the plot width and height
+
+ bounds: tuple(float)
+ Bounds in the form (lon_min, lat_min, lon_max, lat_max)
+ width: int
+ Width of the overall plot
+ height: int
+ Height of the overall plot
+ tile_width: int (default=256)
+ Width of each tile
+ tile_width: int (default=256)
+ Height of each tile
+ max_zoom: int (default=21)
+ Maximum allowed zoom level
+ """
+
+ def latRad(lat):
+ sin = math.sin(lat * math.pi / 180);
+ if sin == 1:
+ radX2 = 20
+ else:
+ radX2 = math.log((1 + sin) / (1 - sin)) / 2;
+ return max(min(radX2, math.pi), -math.pi) / 2;
+
+ def zoom(mapPx, worldPx, fraction):
+ return math.floor(math.log(mapPx / worldPx / fraction) / math.log(2));
+
+ x0, y0, x1, y1 = bounds
+ latFraction = (latRad(y1) - latRad(y0)) / math.pi
+ lngDiff = x1 - x0
+ lngFraction = ((lngDiff + 360) if lngDiff < 0 else lngDiff)/360
+ latZoom = zoom(height, tile_height, latFraction)
+ lngZoom = zoom(width, tile_width, lngFraction)
+ return min(latZoom, lngZoom, max_zoom)
+
+ @param.depends('tile_server')
+ def callback(self):
+ return (gv.WMTS(self.tile_server) * gv.tile_sources.StamenLabels())
+
+ @property
+ def bbox(self):
+ element = self.box_stream.element if self.box_stream.data else self.boxes
+ # Update shared_state with bounding box (if any)
+ if element:
+ xs, ys = element.array().T
+ bbox = (xs[0], ys[0], xs[2], ys[1])
+ # Set the zoom level
+ zoom_level = self.bounds_to_zoom_level(bbox, self.width, self.height)
+ self.zoom_level = zoom_level + self.magnification
+ return bbox
+ else:
+ return None
+
+ # UPDATE wrt earthsim: add the get_image method that replaces the former get_tiff method.
+ # get_image uses get_tile_rgb, a new util function from geoviews, while get_tiff
+ # required the quest library as an external dependency.
+ def get_image(self):
+ bbox = self.bbox
+ if bbox is None:
+ raise ValueError('Please supply a bounding box in order to extract an image.')
+
+ img = gv.util.get_tile_rgb(
+ tile_source=self.tile_server,
+ bbox=bbox,
+ zoom_level=self.zoom_level,
+ bbox_crs=ccrs.PlateCarree(),
+ )
+
+ return img
+
+ def view(self):
+ return (gv.DynamicMap(self.callback) * self.boxes).opts(active_tools=['wheel_zoom'])
+
+ @param.output(image=hv.Image)
+ def output(self):
+ return self.get_image()
+
+ def panel(self):
+ return pn.Row(self.param, self.view())
+
+
+options = Store.options('bokeh')
+
+options.Points = Options('plot', padding=0.1)
+options.Path = Options('plot', padding=0.1)
+options.Polygons = Options('plot', padding=0.1)
diff --git a/grabcut/thumbnails/grabcut.png b/grabcut/thumbnails/grabcut.png
new file mode 100644
index 000000000..1d26aa7ab
Binary files /dev/null and b/grabcut/thumbnails/grabcut.png differ