diff --git a/.mypy.ini b/.mypy.ini index 6284e54b..be6968bc 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -21,9 +21,30 @@ disallow_untyped_defs = True ############################################################################### # TPL-module options +# Should only include exclusions for non-optional dependencies. Modules that +# utilize optional dependencies should `type: ignore` within their module +# scope. + +[mypy-cv2.*] +ignore_missing_imports = True + +[mypy-matplotlib.*] +ignore_missing_imports = True + +[mypy-PIL.*] +ignore_missing_imports = True [mypy-pkg_resources.extern] ignore_missing_imports = True [mypy-pytest] ignore_missing_imports = True + +[mypy-skimage.*] +ignore_missing_imports = True + +[mypy-sklearn.*] +ignore_missing_imports = True + +[mypy-tqdm.*] +ignore_missing_imports = True diff --git a/poetry.lock b/poetry.lock index 291c00e4..55f91a40 100644 --- a/poetry.lock +++ b/poetry.lock @@ -90,11 +90,22 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] toml = ["toml"] +[[package]] +name = "cycler" +version = "0.10.0" +description = "Composable style cycles" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + [[package]] name = "decorator" version = "5.0.7" description = "Decorators for Humans" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -139,6 +150,25 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "imageio" +version = "2.9.0" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +numpy = "*" +pillow = "*" + +[package.extras] +ffmpeg = ["imageio-ffmpeg"] +fits = ["astropy"] +full = ["astropy", "gdal", "imageio-ffmpeg", "itk"] +gdal = ["gdal"] +itk = ["itk"] + [[package]] name = "imagesize" version = "1.2.0" @@ -239,6 +269,22 @@ MarkupSafe = ">=0.23" [package.extras] i18n = ["Babel (>=0.8)"] +[[package]] +name = "joblib" +version = "1.0.1" +description = "Lightweight pipelining with Python functions" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "kiwisolver" +version = "1.3.1" +description = "A fast implementation of the Cassowary constraint solver" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "livereload" version = "2.6.3" @@ -259,6 +305,22 @@ category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +[[package]] +name = "matplotlib" +version = "3.4.1" +description = "Python plotting package" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cycler = ">=0.10" +kiwisolver = ">=1.0.1" +numpy = ">=1.16" +pillow = ">=6.2.0" +pyparsing = ">=2.2.1" +python-dateutil = ">=2.7" + [[package]] name = "mccabe" version = "0.6.1" @@ -291,6 +353,30 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "networkx" +version = "2.5" +description = "Python package for creating and manipulating graphs and networks" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +decorator = ">=4.3.0" + +[package.extras] +all = ["numpy", "scipy", "pandas", "matplotlib", "pygraphviz", "pydot", "pyyaml", "lxml", "pytest"] +gdal = ["gdal"] +lxml = ["lxml"] +matplotlib = ["matplotlib"] +numpy = ["numpy"] +pandas = ["pandas"] +pydot = ["pydot"] +pygraphviz = ["pygraphviz"] +pytest = ["pytest"] +pyyaml = ["pyyaml"] +scipy = ["scipy"] + [[package]] name = "numpy" version = "1.20.2" @@ -299,6 +385,17 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "opencv-python-headless" +version = "4.5.1.48" +description = "Wrapper package for OpenCV python bindings." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +numpy = ">=1.13.3" + [[package]] name = "packaging" version = "20.9" @@ -418,7 +515,7 @@ python-versions = ">=3.5" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -459,6 +556,17 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "pytest-xdist", "virtualenv"] +[[package]] +name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + [[package]] name = "pytz" version = "2021.1" @@ -467,6 +575,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "pywavelets" +version = "1.1.1" +description = "PyWavelets, wavelet transform module" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +numpy = ">=1.13.3" + [[package]] name = "requests" version = "2.25.1" @@ -485,11 +604,66 @@ urllib3 = ">=1.21.1,<1.27" security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +[[package]] +name = "scikit-image" +version = "0.18.1" +description = "Image processing in Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +imageio = ">=2.3.0" +matplotlib = ">=2.0.0,<3.0.0 || >3.0.0" +networkx = ">=2.0" +numpy = ">=1.16.5" +pillow = ">=4.3.0,<7.1.0 || >7.1.0,<7.1.1 || >7.1.1" +PyWavelets = ">=1.1.1" +scipy = ">=1.0.1" +tifffile = ">=2019.7.26" + +[package.extras] +data = ["pooch (>=1.3.0)"] +docs = ["sphinx (>=1.8,<=2.4.4)", "sphinx-gallery (>=0.7.0,!=0.8.0)", "numpydoc (>=1.0)", "sphinx-copybutton", "pytest-runner", "scikit-learn", "matplotlib (>=3.0.1)", "dask[array] (>=0.15.0,!=2.17.0)", "cloudpickle (>=0.2.1)", "pandas (>=0.23.0)", "seaborn (>=0.7.1)", "pooch (>=1.3.0)", "tifffile (>=2020.5.30)", "myst-parser", "ipywidgets", "plotly (>=4.10.0)"] +optional = ["simpleitk", "astropy (>=3.1.2)", "qtpy", "pyamg", "dask[array] (>=1.0.0,!=2.17.0)", "cloudpickle (>=0.2.1)", "pooch (>=1.3.0)"] +test = ["pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-localserver", "pytest-faulthandler", "flake8", "codecov", "pooch (>=1.3.0)"] + +[[package]] +name = "scikit-learn" +version = "0.24.2" +description = "A set of python modules for machine learning and data mining" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +joblib = ">=0.11" +numpy = ">=1.13.3" +scipy = ">=0.19.1" +threadpoolctl = ">=2.0.0" + +[package.extras] +benchmark = ["matplotlib (>=2.1.1)", "pandas (>=0.25.0)", "memory-profiler (>=0.57.0)"] +docs = ["matplotlib (>=2.1.1)", "scikit-image (>=0.13)", "pandas (>=0.25.0)", "seaborn (>=0.9.0)", "memory-profiler (>=0.57.0)", "sphinx (>=3.2.0)", "sphinx-gallery (>=0.7.0)", "numpydoc (>=1.0.0)", "Pillow (>=7.1.2)", "sphinx-prompt (>=1.3.0)"] +examples = ["matplotlib (>=2.1.1)", "scikit-image (>=0.13)", "pandas (>=0.25.0)", "seaborn (>=0.9.0)"] +tests = ["matplotlib (>=2.1.1)", "scikit-image (>=0.13)", "pandas (>=0.25.0)", "pytest (>=5.0.1)", "pytest-cov (>=2.9.0)", "flake8 (>=3.8.2)", "mypy (>=0.770)", "pyamg (>=4.0.0)"] + +[[package]] +name = "scipy" +version = "1.6.1" +description = "SciPy: Scientific Library for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +numpy = ">=1.16.5" + [[package]] name = "six" version = "1.15.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" @@ -675,6 +849,28 @@ python-versions = ">=3.5" lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] +[[package]] +name = "threadpoolctl" +version = "2.1.0" +description = "threadpoolctl" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "tifffile" +version = "2021.4.8" +description = "Read and write TIFF files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +numpy = ">=1.15.1" + +[package.extras] +all = ["imagecodecs (>=2021.3.31)", "matplotlib (>=3.2)", "lxml"] + [[package]] name = "toml" version = "0.10.2" @@ -691,6 +887,19 @@ category = "dev" optional = false python-versions = ">= 3.5" +[[package]] +name = "tqdm" +version = "4.60.0" +description = "Fast, Extensible Progress Meter" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +telegram = ["requests"] + [[package]] name = "traitlets" version = "5.0.5" @@ -757,7 +966,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "a3b7e78407487c0af3dcefebd49e5bccc548b19cb9107ccad0b8d174724a35a1" +content-hash = "accc27b19d356ac8f0f7636b0a1d83de37441679b6ea11524de539fa8007ef5d" [metadata.files] alabaster = [ @@ -850,6 +1059,10 @@ coverage = [ {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"}, {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] +cycler = [ + {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, + {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"}, +] decorator = [ {file = "decorator-5.0.7-py3-none-any.whl", hash = "sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"}, {file = "decorator-5.0.7.tar.gz", hash = "sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060"}, @@ -870,6 +1083,10 @@ idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] +imageio = [ + {file = "imageio-2.9.0-py3-none-any.whl", hash = "sha256:3604d751f03002e8e0e7650aa71d8d9148144a87daf17cb1f3228e80747f2e6b"}, + {file = "imageio-2.9.0.tar.gz", hash = "sha256:52ddbaeca2dccf53ba2d6dec5676ca7bc3b2403ef8b37f7da78b7654bb3e10f0"}, +] imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, @@ -898,6 +1115,44 @@ jinja2 = [ {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] +joblib = [ + {file = "joblib-1.0.1-py3-none-any.whl", hash = "sha256:feeb1ec69c4d45129954f1b7034954241eedfd6ba39b5e9e4b6883be3332d5e5"}, + {file = "joblib-1.0.1.tar.gz", hash = "sha256:9c17567692206d2f3fb9ecf5e991084254fe631665c450b443761c4186a613f7"}, +] +kiwisolver = [ + {file = "kiwisolver-1.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd34fbbfbc40628200730bc1febe30631347103fc8d3d4fa012c21ab9c11eca9"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:d3155d828dec1d43283bd24d3d3e0d9c7c350cdfcc0bd06c0ad1209c1bbc36d0"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5a7a7dbff17e66fac9142ae2ecafb719393aaee6a3768c9de2fd425c63b53e21"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f8d6f8db88049a699817fd9178782867bf22283e3813064302ac59f61d95be05"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:5f6ccd3dd0b9739edcf407514016108e2280769c73a85b9e59aa390046dbf08b"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-win32.whl", hash = "sha256:225e2e18f271e0ed8157d7f4518ffbf99b9450fca398d561eb5c4a87d0986dd9"}, + {file = "kiwisolver-1.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cf8b574c7b9aa060c62116d4181f3a1a4e821b2ec5cbfe3775809474113748d4"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:232c9e11fd7ac3a470d65cd67e4359eee155ec57e822e5220322d7b2ac84fbf0"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b38694dcdac990a743aa654037ff1188c7a9801ac3ccc548d3341014bc5ca278"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ca3820eb7f7faf7f0aa88de0e54681bddcb46e485beb844fcecbcd1c8bd01689"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c8fd0f1ae9d92b42854b2979024d7597685ce4ada367172ed7c09edf2cef9cb8"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:1e1bc12fb773a7b2ffdeb8380609f4f8064777877b2225dec3da711b421fda31"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:72c99e39d005b793fb7d3d4e660aed6b6281b502e8c1eaf8ee8346023c8e03bc"}, + {file = "kiwisolver-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:8be8d84b7d4f2ba4ffff3665bcd0211318aa632395a1a41553250484a871d454"}, + {file = "kiwisolver-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31dfd2ac56edc0ff9ac295193eeaea1c0c923c0355bf948fbd99ed6018010b72"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:563c649cfdef27d081c84e72a03b48ea9408c16657500c312575ae9d9f7bc1c3"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:78751b33595f7f9511952e7e60ce858c6d64db2e062afb325985ddbd34b5c131"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a357fd4f15ee49b4a98b44ec23a34a95f1e00292a139d6015c11f55774ef10de"}, + {file = "kiwisolver-1.3.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:5989db3b3b34b76c09253deeaf7fbc2707616f130e166996606c284395da3f18"}, + {file = "kiwisolver-1.3.1-cp38-cp38-win32.whl", hash = "sha256:c08e95114951dc2090c4a630c2385bef681cacf12636fb0241accdc6b303fd81"}, + {file = "kiwisolver-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:44a62e24d9b01ba94ae7a4a6c3fb215dc4af1dde817e7498d901e229aaf50e4e"}, + {file = "kiwisolver-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:50af681a36b2a1dee1d3c169ade9fdc59207d3c31e522519181e12f1b3ba7000"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a53d27d0c2a0ebd07e395e56a1fbdf75ffedc4a05943daf472af163413ce9598"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:834ee27348c4aefc20b479335fd422a2c69db55f7d9ab61721ac8cd83eb78882"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c3e6455341008a054cccee8c5d24481bcfe1acdbc9add30aa95798e95c65621"}, + {file = "kiwisolver-1.3.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:acef3d59d47dd85ecf909c359d0fd2c81ed33bdff70216d3956b463e12c38a54"}, + {file = "kiwisolver-1.3.1-cp39-cp39-win32.whl", hash = "sha256:c5518d51a0735b1e6cee1fdce66359f8d2b59c3ca85dc2b0813a8aa86818a030"}, + {file = "kiwisolver-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b9edd0110a77fc321ab090aaa1cfcaba1d8499850a12848b81be2222eab648f6"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0cd53f403202159b44528498de18f9285b04482bab2a6fc3f5dd8dbb9352e30d"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:33449715e0101e4d34f64990352bce4095c8bf13bed1b390773fc0a7295967b3"}, + {file = "kiwisolver-1.3.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:401a2e9afa8588589775fe34fc22d918ae839aaaf0c0e96441c0fdbce6d8ebe6"}, + {file = "kiwisolver-1.3.1.tar.gz", hash = "sha256:950a199911a8d94683a6b10321f9345d5a3a8433ec58b217ace979e18f16e248"}, +] livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] @@ -955,6 +1210,27 @@ markupsafe = [ {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] +matplotlib = [ + {file = "matplotlib-3.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a54efd6fcad9cb3cd5ef2064b5a3eeb0b63c99f26c346bdcf66e7c98294d7cc"}, + {file = "matplotlib-3.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:86dc94e44403fa0f2b1dd76c9794d66a34e821361962fe7c4e078746362e3b14"}, + {file = "matplotlib-3.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:574306171b84cd6854c83dc87bc353cacc0f60184149fb00c9ea871eca8c1ecb"}, + {file = "matplotlib-3.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:84a10e462120aa7d9eb6186b50917ed5a6286ee61157bfc17c5b47987d1a9068"}, + {file = "matplotlib-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:81e6fe8b18ef5be67f40a1d4f07d5a4ed21d3878530193898449ddef7793952f"}, + {file = "matplotlib-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c45e7bf89ea33a2adaef34774df4e692c7436a18a48bcb0e47a53e698a39fa39"}, + {file = "matplotlib-3.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1f83a32e4b6045191f9d34e4dc68c0a17c870b57ef9cca518e516da591246e79"}, + {file = "matplotlib-3.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a18cc1ab4a35b845cf33b7880c979f5c609fd26c2d6e74ddfacb73dcc60dd956"}, + {file = "matplotlib-3.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ac2a30a09984c2719f112a574b6543ccb82d020fd1b23b4d55bf4759ba8dd8f5"}, + {file = "matplotlib-3.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a97781453ac79409ddf455fccf344860719d95142f9c334f2a8f3fff049ffec3"}, + {file = "matplotlib-3.4.1-cp38-cp38-win32.whl", hash = "sha256:2eee37340ca1b353e0a43a33da79d0cd4bcb087064a0c3c3d1329cdea8fbc6f3"}, + {file = "matplotlib-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:90dbc007f6389bcfd9ef4fe5d4c78c8d2efe4e0ebefd48b4f221cdfed5672be2"}, + {file = "matplotlib-3.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f16660edf9a8bcc0f766f51c9e1b9d2dc6ceff6bf636d2dbd8eb925d5832dfd"}, + {file = "matplotlib-3.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a989022f89cda417f82dbf65e0a830832afd8af743d05d1414fb49549287ff04"}, + {file = "matplotlib-3.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:be4430b33b25e127fc4ea239cc386389de420be4d63e71d5359c20b562951ce1"}, + {file = "matplotlib-3.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:7561fd541477d41f3aa09457c434dd1f7604f3bd26d7858d52018f5dfe1c06d1"}, + {file = "matplotlib-3.4.1-cp39-cp39-win32.whl", hash = "sha256:9f374961a3996c2d1b41ba3145462c3708a89759e604112073ed6c8bdf9f622f"}, + {file = "matplotlib-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:53ceb12ef44f8982b45adc7a0889a7e2df1d758e8b360f460e435abe8a8cd658"}, + {file = "matplotlib-3.4.1.tar.gz", hash = "sha256:84d4c4f650f356678a5d658a43ca21a41fca13f9b8b00169c0b76e6a6a948908"}, +] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -987,6 +1263,10 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] +networkx = [ + {file = "networkx-2.5-py3-none-any.whl", hash = "sha256:8c5812e9f798d37c50570d15c4a69d5710a18d77bafc903ee9c5fba7454c616c"}, + {file = "networkx-2.5.tar.gz", hash = "sha256:7978955423fbc9639c10498878be59caf99b44dc304c2286162fd24b458c1602"}, +] numpy = [ {file = "numpy-1.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935"}, {file = "numpy-1.20.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6"}, @@ -1013,6 +1293,33 @@ numpy = [ {file = "numpy-1.20.2-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042"}, {file = "numpy-1.20.2.zip", hash = "sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee"}, ] +opencv-python-headless = [ + {file = "opencv-python-headless-4.5.1.48.tar.gz", hash = "sha256:d16825755e7b5a6d8737f93e116670229e1510199e0af9213004e187ae0dbcc5"}, + {file = "opencv_python_headless-4.5.1.48-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:f2011ecb3980bbed283d17d43e0f1221bac88c0cac1a6fb59a056544de2df2f7"}, + {file = "opencv_python_headless-4.5.1.48-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:777fb596e04331f73ef5b0c1faa4d33348f29ca58216d9286355c16f5489c939"}, + {file = "opencv_python_headless-4.5.1.48-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:924aa4c34ead0b817309f42291dc526b2a7755476afe3009d1e275fc3090d92d"}, + {file = "opencv_python_headless-4.5.1.48-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0e02809db2968e54f3c23340be6ff9a1428b3f03f5dca7cd5aceda66e319ce86"}, + {file = "opencv_python_headless-4.5.1.48-cp36-cp36m-win32.whl", hash = "sha256:522f12dd994e064a30562adfd63b9439099bd7c80819f5261c37ebe593283c9e"}, + {file = "opencv_python_headless-4.5.1.48-cp36-cp36m-win_amd64.whl", hash = "sha256:e3027e0d1b71b68b5cfe1ea9c627e323dff71112c854ba19805258d8fc6c630e"}, + {file = "opencv_python_headless-4.5.1.48-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:f5e40a06116460ef2fd2d1c24be3b65f8bfb5fcdfe433f3fc01bcb4c2eb485bf"}, + {file = "opencv_python_headless-4.5.1.48-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:ba2f0bd46e9534f29969e39f7895cbea9764173102b0c04b7818b8a9910d66e4"}, + {file = "opencv_python_headless-4.5.1.48-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:f7f8e4f7c63c8e95eb210f4cba88d0069a0a964d6335d7a35b07f0d0baa13558"}, + {file = "opencv_python_headless-4.5.1.48-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:aa562c520f46283423ba8fac29099458e42deab697a9abd0491622e421c5c454"}, + {file = "opencv_python_headless-4.5.1.48-cp37-cp37m-win32.whl", hash = "sha256:657cdd9fbbbbb7e898ae3d9b0649b367d0e440d429c714104d069c5612d578bb"}, + {file = "opencv_python_headless-4.5.1.48-cp37-cp37m-win_amd64.whl", hash = "sha256:fe02a943b1a28b505e954fbce24e867119a3eb4351f93adad55c6cfe81a70484"}, + {file = "opencv_python_headless-4.5.1.48-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:526b9e19cf6300f0891d8f427eac1048091912332bb781eb4957a4994bfbe608"}, + {file = "opencv_python_headless-4.5.1.48-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5f0c7d34fa9953706c2a1a6d2760a91dc5a68cab3df16af61609894c6c8586f1"}, + {file = "opencv_python_headless-4.5.1.48-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:1f7c14f5d4e5af4dc4669fc6b4a983b36072a934c439ac11266b930496da8255"}, + {file = "opencv_python_headless-4.5.1.48-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:7c75680ffdf32d7044415a215d1fc60dbec14a7f2f0b59a85f0f74ef5efcb6ad"}, + {file = "opencv_python_headless-4.5.1.48-cp38-cp38-win32.whl", hash = "sha256:2560dcf3c1158226b066302f777bfe0f65282410b8d90871dd872306c967d1f1"}, + {file = "opencv_python_headless-4.5.1.48-cp38-cp38-win_amd64.whl", hash = "sha256:9aa04a491c534531029fbac61da961fae0bf4abb1786eed9c91befde4ca7bd81"}, + {file = "opencv_python_headless-4.5.1.48-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:96d1da6ad061d8f3509668d398d14dd8265e529d6407f87eb26e7f4ddf043cc0"}, + {file = "opencv_python_headless-4.5.1.48-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:243aea91cc1e36a47c46da4cc408071af39444a48df1fe1539ea8d7990500fd2"}, + {file = "opencv_python_headless-4.5.1.48-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2e6a9a88617a0ef7219cff24ba78a58416670a77e6ac63975f9009af3319ab63"}, + {file = "opencv_python_headless-4.5.1.48-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:372149d007e20bf556b7687591d22b58b56b3c225f492da051d81587e5dc7411"}, + {file = "opencv_python_headless-4.5.1.48-cp39-cp39-win32.whl", hash = "sha256:65c9ea57be5ebcaed009b3fee14fe59f3b6aa1e573fe08c5039ff057ad593c53"}, + {file = "opencv_python_headless-4.5.1.48-cp39-cp39-win_amd64.whl", hash = "sha256:a322d4df14c3a5f19701a023b3da91e9b8af8653b0d2ee0c50b0b341212f7343"}, +] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, @@ -1104,14 +1411,124 @@ pytest-cov = [ {file = "pytest-cov-2.11.1.tar.gz", hash = "sha256:359952d9d39b9f822d9d29324483e7ba04a3a17dd7d05aa6beb7ea01e359e5f7"}, {file = "pytest_cov-2.11.1-py2.py3-none-any.whl", hash = "sha256:bdb9fdb0b85a7cc825269a4c56b48ccaa5c7e365054b6038772c32ddcdc969da"}, ] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] pytz = [ {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] +pywavelets = [ + {file = "PyWavelets-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:35959c041ec014648575085a97b498eafbbaa824f86f6e4a59bfdef8a3fe6308"}, + {file = "PyWavelets-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:55e39ec848ceec13c9fa1598253ae9dd5c31d09dfd48059462860d2b908fb224"}, + {file = "PyWavelets-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c06d2e340c7bf8b9ec71da2284beab8519a3908eab031f4ea126e8ccfc3fd567"}, + {file = "PyWavelets-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:be105382961745f88d8196bba5a69ee2c4455d87ad2a2e5d1eed6bd7fda4d3fd"}, + {file = "PyWavelets-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:076ca8907001fdfe4205484f719d12b4a0262dfe6652fa1cfc3c5c362d14dc84"}, + {file = "PyWavelets-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7947e51ca05489b85928af52a34fe67022ab5b81d4ae32a4109a99e883a0635e"}, + {file = "PyWavelets-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9e2528823ccf5a0a1d23262dfefe5034dce89cd84e4e124dc553dfcdf63ebb92"}, + {file = "PyWavelets-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:80b924edbc012ded8aa8b91cb2fd6207fb1a9a3a377beb4049b8a07445cec6f0"}, + {file = "PyWavelets-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c2a799e79cee81a862216c47e5623c97b95f1abee8dd1f9eed736df23fb653fb"}, + {file = "PyWavelets-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:d510aef84d9852653d079c84f2f81a82d5d09815e625f35c95714e7364570ad4"}, + {file = "PyWavelets-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:889d4c5c5205a9c90118c1980df526857929841df33e4cd1ff1eff77c6817a65"}, + {file = "PyWavelets-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:68b5c33741d26c827074b3d8f0251de1c3019bb9567b8d303eb093c822ce28f1"}, + {file = "PyWavelets-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18a51b3f9416a2ae6e9a35c4af32cf520dd7895f2b69714f4aa2f4342fca47f9"}, + {file = "PyWavelets-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cfe79844526dd92e3ecc9490b5031fca5f8ab607e1e858feba232b1b788ff0ea"}, + {file = "PyWavelets-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:2f7429eeb5bf9c7068002d0d7f094ed654c77a70ce5e6198737fd68ab85f8311"}, + {file = "PyWavelets-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:720dbcdd3d91c6dfead79c80bf8b00a1d8aa4e5d551dc528c6d5151e4efc3403"}, + {file = "PyWavelets-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:bc5e87b72371da87c9bebc68e54882aada9c3114e640de180f62d5da95749cd3"}, + {file = "PyWavelets-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:98b2669c5af842a70cfab33a7043fcb5e7535a690a00cd251b44c9be0be418e5"}, + {file = "PyWavelets-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e02a0558e0c2ac8b8bbe6a6ac18c136767ec56b96a321e0dfde2173adfa5a504"}, + {file = "PyWavelets-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6162dc0ae04669ea04b4b51420777b9ea2d30b0a9d02901b2a3b4d61d159c2e9"}, + {file = "PyWavelets-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:39c74740718e420d38c78ca4498568fa57976d78d5096277358e0fa9629a7aea"}, + {file = "PyWavelets-1.1.1-cp38-cp38-win32.whl", hash = "sha256:79f5b54f9dc353e5ee47f0c3f02bebd2c899d49780633aa771fed43fa20b3149"}, + {file = "PyWavelets-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:935ff247b8b78bdf77647fee962b1cc208c51a7b229db30b9ba5f6da3e675178"}, + {file = "PyWavelets-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6ebfefebb5c6494a3af41ad8c60248a95da267a24b79ed143723d4502b1fe4d7"}, + {file = "PyWavelets-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6bc78fb9c42a716309b4ace56f51965d8b5662c3ba19d4591749f31773db1125"}, + {file = "PyWavelets-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:411e17ca6ed8cf5e18a7ca5ee06a91c25800cc6c58c77986202abf98d749273a"}, + {file = "PyWavelets-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:83c5e3eb78ce111c2f0b45f46106cc697c3cb6c4e5f51308e1f81b512c70c8fb"}, + {file = "PyWavelets-1.1.1-cp39-cp39-win32.whl", hash = "sha256:2b634a54241c190ee989a4af87669d377b37c91bcc9cf0efe33c10ff847f7841"}, + {file = "PyWavelets-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:732bab78435c48be5d6bc75486ef629d7c8f112e07b313bf1f1a2220ab437277"}, + {file = "PyWavelets-1.1.1.tar.gz", hash = "sha256:1a64b40f6acb4ffbaccce0545d7fc641744f95351f62e4c6aaa40549326008c9"}, +] requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] +scikit-image = [ + {file = "scikit-image-0.18.1.tar.gz", hash = "sha256:fbb618ca911867bce45574c1639618cdfb5d94e207432b19bc19563d80d2f171"}, + {file = "scikit_image-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1cd05c882ffb2a271a1f20b4afe937d63d55b8753c3d652f11495883a7800ebe"}, + {file = "scikit_image-0.18.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e972c628ad9ba52c298b032368e29af9bd5eeb81ce33bc2d9b039a81661c99c5"}, + {file = "scikit_image-0.18.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1256017c513e8e1b8b9da73e5fd1e605d0077bbbc8e5c8d6c2cab36400131c6c"}, + {file = "scikit_image-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:ec25e4110951d3a280421bb10dd510a082ba83d86e20d706294faf7899cdb3d5"}, + {file = "scikit_image-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2eea42706a25ae6e0cebaf1914e2ab1c04061b1f3c9966d76025d58a2e9188fc"}, + {file = "scikit_image-0.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:76446e2402e64d7dba78eeae8aa86e92a0cafe5b1c9e6235bd8d067471ed2788"}, + {file = "scikit_image-0.18.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d5ad4a9b4c9797d4c4c48f45fa224c5ebff22b9b0af636c3ecb8addbb66c21e6"}, + {file = "scikit_image-0.18.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23f9178b21c752bfb4e4ea3a3fa0ff79bc5a401bc75ddb4661f2cebd1c2b0e24"}, + {file = "scikit_image-0.18.1-cp38-cp38-win32.whl", hash = "sha256:d746540cafe7776c6d05a0b40ec744bb8d33d1ddc51faba601d26c02593d8bcc"}, + {file = "scikit_image-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:30447af3f5b7c9491f2d3db5bc275493d1b91bf1dd16b67e2fd79a6bb95d8ee9"}, + {file = "scikit_image-0.18.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae6659b3a8bd4bba7e9dcbfd0064e443b32c7054bf09174749db896730fcf42e"}, + {file = "scikit_image-0.18.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c058770c6ad6e0fe6c30f59970c9c65fa740ff014d121d8c341664cd792cf49"}, + {file = "scikit_image-0.18.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:c700336a7f96109c74154090c5e693693a8e3fa09ed6156a5996cdc9a3bb1534"}, + {file = "scikit_image-0.18.1-cp39-cp39-win32.whl", hash = "sha256:3515b890e771f99bbe1051a0dcfe0fc477da961da933c34f89808a0f1eeb7dc2"}, + {file = "scikit_image-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f602779258807d03e72c0a439cfb221f647e628be166fb3594397435f13c76b"}, +] +scikit-learn = [ + {file = "scikit-learn-0.24.2.tar.gz", hash = "sha256:d14701a12417930392cd3898e9646cf5670c190b933625ebe7511b1f7d7b8736"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:d5bf9c863ba4717b3917b5227463ee06860fc43931dc9026747de416c0a10fee"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5beaeb091071625e83f5905192d8aecde65ba2f26f8b6719845bbf586f7a04a1"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:06ffdcaaf81e2a3b1b50c3ac6842cfb13df2d8b737d61f64643ed61da7389cde"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:fec42690a2eb646b384eafb021c425fab48991587edb412d4db77acc358b27ce"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:5ff3e4e4cf7592d36541edec434e09fb8ab9ba6b47608c4ffe30c9038d301897"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:3cbd734e1aefc7c5080e6b6973fe062f97c26a1cdf1a991037ca196ce1c8f427"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-win32.whl", hash = "sha256:f74429a07fedb36a03c159332b914e6de757176064f9fed94b5f79ebac07d913"}, + {file = "scikit_learn-0.24.2-cp36-cp36m-win_amd64.whl", hash = "sha256:dd968a174aa82f3341a615a033fa6a8169e9320cbb46130686562db132d7f1f0"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:49ec0b1361da328da9bb7f1a162836028e72556356adeb53342f8fae6b450d47"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f18c3ed484eeeaa43a0d45dc2efb4d00fc6542ccdcfa2c45d7b635096a2ae534"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cdf24c1b9bbeb4936456b42ac5bd32c60bb194a344951acb6bfb0cddee5439a4"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d177fe1ff47cc235942d628d41ee5b1c6930d8f009f1a451c39b5411e8d0d4cf"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f3ec00f023d84526381ad0c0f2cff982852d035c921bbf8ceb994f4886c00c64"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:ae19ac105cf7ce8c205a46166992fdec88081d6e783ab6e38ecfbe45729f3c39"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-win32.whl", hash = "sha256:f0ed4483c258fb23150e31b91ea7d25ff8495dba108aea0b0d4206a777705350"}, + {file = "scikit_learn-0.24.2-cp37-cp37m-win_amd64.whl", hash = "sha256:39b7e3b71bcb1fe46397185d6c1a5db1c441e71c23c91a31e7ad8cc3f7305f9a"}, + {file = "scikit_learn-0.24.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:90a297330f608adeb4d2e9786c6fda395d3150739deb3d42a86d9a4c2d15bc1d"}, + {file = "scikit_learn-0.24.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f1d2108e770907540b5248977e4cff9ffaf0f73d0d13445ee938df06ca7579c6"}, + {file = "scikit_learn-0.24.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1eec963fe9ffc827442c2e9333227c4d49749a44e592f305398c1db5c1563393"}, + {file = "scikit_learn-0.24.2-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:2db429090b98045d71218a9ba913cc9b3fe78e0ba0b6b647d8748bc6d5a44080"}, + {file = "scikit_learn-0.24.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:62214d2954377fcf3f31ec867dd4e436df80121e7a32947a0b3244f58f45e455"}, + {file = "scikit_learn-0.24.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8fac72b9688176922f9f54fda1ba5f7ffd28cbeb9aad282760186e8ceba9139a"}, + {file = "scikit_learn-0.24.2-cp38-cp38-win32.whl", hash = "sha256:ae426e3a52842c6b6d77d00f906b6031c8c2cfdfabd6af7511bb4bc9a68d720e"}, + {file = "scikit_learn-0.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:038f4e9d6ef10e1f3fe82addc3a14735c299866eb10f2c77c090410904828312"}, + {file = "scikit_learn-0.24.2-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:48f273836e19901ba2beecd919f7b352f09310ce67c762f6e53bc6b81cacf1f0"}, + {file = "scikit_learn-0.24.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:a2a47449093dcf70babc930beba2ca0423cb7df2fa5fd76be5260703d67fa574"}, + {file = "scikit_learn-0.24.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0e71ce9c7cbc20f6f8b860107ce15114da26e8675238b4b82b7e7cd37ca0c087"}, + {file = "scikit_learn-0.24.2-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2754c85b2287333f9719db7f23fb7e357f436deed512db3417a02bf6f2830aa5"}, + {file = "scikit_learn-0.24.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7be1b88c23cfac46e06404582215a917017cd2edaa2e4d40abe6aaff5458f24b"}, + {file = "scikit_learn-0.24.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:4e6198675a6f9d333774671bd536668680eea78e2e81c0b19e57224f58d17f37"}, + {file = "scikit_learn-0.24.2-cp39-cp39-win32.whl", hash = "sha256:cbdb0b3db99dd1d5f69d31b4234367d55475add31df4d84a3bd690ef017b55e2"}, + {file = "scikit_learn-0.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:40556bea1ef26ef54bc678d00cf138a63069144a0b5f3a436eecd8f3468b903e"}, +] +scipy = [ + {file = "scipy-1.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a15a1f3fc0abff33e792d6049161b7795909b40b97c6cc2934ed54384017ab76"}, + {file = "scipy-1.6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e79570979ccdc3d165456dd62041d9556fb9733b86b4b6d818af7a0afc15f092"}, + {file = "scipy-1.6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a423533c55fec61456dedee7b6ee7dce0bb6bfa395424ea374d25afa262be261"}, + {file = "scipy-1.6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:33d6b7df40d197bdd3049d64e8e680227151673465e5d85723b3b8f6b15a6ced"}, + {file = "scipy-1.6.1-cp37-cp37m-win32.whl", hash = "sha256:6725e3fbb47da428794f243864f2297462e9ee448297c93ed1dcbc44335feb78"}, + {file = "scipy-1.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5fa9c6530b1661f1370bcd332a1e62ca7881785cc0f80c0d559b636567fab63c"}, + {file = "scipy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd50daf727f7c195e26f27467c85ce653d41df4358a25b32434a50d8870fc519"}, + {file = "scipy-1.6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:f46dd15335e8a320b0fb4685f58b7471702234cba8bb3442b69a3e1dc329c345"}, + {file = "scipy-1.6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0e5b0ccf63155d90da576edd2768b66fb276446c371b73841e3503be1d63fb5d"}, + {file = "scipy-1.6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2481efbb3740977e3c831edfd0bd9867be26387cacf24eb5e366a6a374d3d00d"}, + {file = "scipy-1.6.1-cp38-cp38-win32.whl", hash = "sha256:68cb4c424112cd4be886b4d979c5497fba190714085f46b8ae67a5e4416c32b4"}, + {file = "scipy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:5f331eeed0297232d2e6eea51b54e8278ed8bb10b099f69c44e2558c090d06bf"}, + {file = "scipy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8a51d33556bf70367452d4d601d1742c0e806cd0194785914daf19775f0e67"}, + {file = "scipy-1.6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:83bf7c16245c15bc58ee76c5418e46ea1811edcc2e2b03041b804e46084ab627"}, + {file = "scipy-1.6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:794e768cc5f779736593046c9714e0f3a5940bc6dcc1dba885ad64cbfb28e9f0"}, + {file = "scipy-1.6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5da5471aed911fe7e52b86bf9ea32fb55ae93e2f0fac66c32e58897cfb02fa07"}, + {file = "scipy-1.6.1-cp39-cp39-win32.whl", hash = "sha256:8e403a337749ed40af60e537cc4d4c03febddcc56cd26e774c9b1b600a70d3e4"}, + {file = "scipy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a5193a098ae9f29af283dcf0041f762601faf2e595c0db1da929875b7570353f"}, + {file = "scipy-1.6.1.tar.gz", hash = "sha256:c4fceb864890b6168e79b0e714c585dbe2fd4222768ee90bc1aa0f8218691b11"}, +] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, @@ -1166,6 +1583,14 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, ] +threadpoolctl = [ + {file = "threadpoolctl-2.1.0-py3-none-any.whl", hash = "sha256:38b74ca20ff3bb42caca8b00055111d74159ee95c4370882bbff2b93d24da725"}, + {file = "threadpoolctl-2.1.0.tar.gz", hash = "sha256:ddc57c96a38beb63db45d6c159b5ab07b6bced12c45a1f07b2b92f272aebfa6b"}, +] +tifffile = [ + {file = "tifffile-2021.4.8-py3-none-any.whl", hash = "sha256:1cfc55f5b728e200142580a7bf108b72775c4097d007b4111876559fa1fb7432"}, + {file = "tifffile-2021.4.8.tar.gz", hash = "sha256:55aa8baad38e1567c9fe450fff52160e4a21294a612f241c5e414da80f87209b"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -1213,6 +1638,10 @@ tornado = [ {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, ] +tqdm = [ + {file = "tqdm-4.60.0-py2.py3-none-any.whl", hash = "sha256:daec693491c52e9498632dfbe9ccfc4882a557f5fa08982db1b4d3adbe0887c3"}, + {file = "tqdm-4.60.0.tar.gz", hash = "sha256:ebdebdb95e3477ceea267decfc0784859aa3df3e27e22d23b83e9b272bf157ae"}, +] traitlets = [ {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, diff --git a/pyproject.toml b/pyproject.toml index 4e6e49ec..f5a1abca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,9 +32,14 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.7" +matplotlib = "^3.4.1" +opencv-python-headless = "^4.5.1" +scikit-image = "^0.18.1" +scikit-learn = "^0.24.2" smqtk-core = "^0.17.0" smqtk-dataprovider = "^0.15.0" smqtk-descriptors = "^0.15.0" +tqdm = "^4.60.0" [tool.poetry.dev-dependencies] # CI diff --git a/xaitk_saliency/impls/saliency/logit_sal.py b/xaitk_saliency/impls/saliency/logit_sal.py index 41173eda..673547e3 100644 --- a/xaitk_saliency/impls/saliency/logit_sal.py +++ b/xaitk_saliency/impls/saliency/logit_sal.py @@ -1,100 +1,82 @@ -from PIL import Image +import io +import logging +from typing import Any, Dict, Iterable, Sequence, Tuple, Type + +import PIL.Image import numpy as np -import os -from sklearn.metrics.pairwise import euclidean_distances -import six -from smqtk.algorithms.relevancy_index import get_relevancy_index_impls -from smqtk.algorithms import get_descriptor_generator_impls -from smqtk.algorithms import SmqtkAlgorithm -from smqtk.algorithms.saliency import SaliencyBlackbox,ImageSaliencyAugmenter -from smqtk.representation.data_element.memory_element import DataMemoryElement -from smqtk.utils import plugin +from smqtk_core.configuration import ( + from_config_dict, + to_config_dict, +) +from smqtk_dataprovider.impls.data_element.memory import DataMemoryElement +from smqtk_descriptors import DescriptorElement, DescriptorGenerator + +from xaitk_saliency.interfaces.saliency import ( + ImageSaliencyAugmenter, + SaliencyBlackbox, +) +from xaitk_saliency.utils.masking import generate_masked_images + + +LOG = logging.getLogger(__name__) + - -class Logit_SaliencyBlackbox (SaliencyBlackbox): +class LogitSaliencyBlackbox(SaliencyBlackbox): """ - Logit_SaliencyBlackbox function yields some floating point scalar value - for a given masked base image descriptor element that signifies the + This implementation yields some floating point scalar value + for a given masked base image descriptor element that signifies the difference between the confidence value of the query image and masked image descriptors, used by class implementations of 'ImageSaliencyMapGenerator'. """ - def __init__(self, pos_descriptors, neg_descriptors, rel_index, T_descr): + def __init__( + self, + pos_descriptors: Sequence[DescriptorElement], + neg_descriptors: Sequence[DescriptorElement], + rel_index: Any, # "RelevancyIndex"; TODO: switch to RankRelevancy + T_descr: DescriptorElement + ): + """ + :param pos_descriptors: is a set of positive descriptors. + :param neg_descriptors: is a set of negative descriptors. + :param rel_index: + Plugin implementation of the algorithms used to generate relevance + index used to rank images. + :param T_descr: + Base image feature descriptor """ - :param list[smqtk.representation.DescriptorMemoryElement] - pos_descriptors: is a set of positive descriptors. - :param list[smqtk.representation.DescriptorMemoryElement] - neg_descriptors: is a set of negative descriptors. - :param rel_index: Plugin implementation of the algorithms used to - generate relevance index used to rank images - :type: A new instance of a class implementing the - ``RelevancyIndex`` class. - :param smqtk.representation.DescriptorElement - T_descr: Base image feature descriptor - """ - self.pos_de = pos_descriptors - self.neg_de = neg_descriptors + self.neg_de = neg_descriptors self.ADJs = (self.pos_de, self.neg_de) self.rel_index = rel_index self.T_descr = T_descr - - @classmethod - def is_usable(self): - """ - Check whether this implementation is available for use. - Required valid presence of Ajudications and a new instance of - a class implementing the ``RelevancyIndex`` class. - :return: Boolean determination of whether implementation is usable. - :rtype: bool - """ - valid = True - return valid - @classmethod - def from_iqr_session(cls, iqrs, descr_gen, base_image): - """ - Create an ``SaliencyBlackbox`` instance from iqrs session, descriptor - generator and base_image. - :param smqtk.iqr.IqrSession iqrs:`smqtk.iqr.IqrSession` instance. - :param smqtk.algorithms.DescriptorGenerator descr_gen: - The descriptor generator used by smqtk. - :param PIL.Image base_image: The Base image for which we - need to calculate a saliency map. - :return: A new instance of a class implementing the - ``SaliencyBlackbox`` class. - :rtype: SaliencyBlackbox - """ - + def from_iqr_session( + cls: Type["LogitSaliencyBlackbox"], + iqrs: Any, # "IqrSession" + descr_generator: DescriptorGenerator, + base_image: PIL.Image.Image + ) -> "LogitSaliencyBlackbox": assert iqrs - pos = list(iqrs.positive_descriptors |\ - iqrs.external_positive_descriptors) - neg = list(iqrs.negative_descriptors |\ - iqrs.external_negative_descriptors) - rel_index = plugin.from_plugin_config\ - (plugin.to_plugin_config(iqrs.rel_index),\ - get_relevancy_index_impls()) - buff = six.BytesIO() + pos = list(iqrs.positive_descriptors | iqrs.external_positive_descriptors) + neg = list(iqrs.negative_descriptors | + iqrs.external_negative_descriptors) + # TODO: This requires the relevancy package breakout for the types used + # here. + rel_index: Any = from_config_dict( + to_config_dict(iqrs.rel_index), + [] # RelevancyIndex.get_impls() + ) + buff = io.BytesIO() base_image.save(buff, format="bmp") de = DataMemoryElement(buff.getvalue(), content_type='image/bmp') - T_descr=descr_gen.compute_descriptor(de) - return Logit_SaliencyBlackbox(pos, neg, rel_index, T_descr) + T_descr = descr_generator.generate_one_element(de) + return LogitSaliencyBlackbox(pos, neg, rel_index, T_descr) - def get_config(self): - """ - Returns a JSON-compliant dictionary that could be passed to - the class's ``from_config`` method to produce an instance - with identical configuration. - In the common case, this involves naming the keys of the dictionary - based on the initialization argument names as if it were to be passed - to the constructor via dictionary expansion. - :return: JSON type compliant configuration dictionary. - :rtype: dict - """ - + def get_config(self) -> Dict[str, Any]: return { 'pos_descriptors': self.pos_de, 'neg_descriptors': self.neg_de, @@ -102,102 +84,74 @@ def get_config(self): 'T_descr': self.T_descr, } - def transform(self, descriptors): - """ - Transform some descriptor element into a saliency scalar. - :param Iterable[smqtk.representation.DescriptorElement] - descriptors:Descriptor of augmentations to get their scalar value. - :return: The saliency value for the given descriptor. - :rtype: numpy.ndarray[float] - """ - - descriptors_list=list(descriptors) - rel_train_set=[single_descr for single_descr in descriptors_list] + def transform(self, descriptors: Iterable[DescriptorElement]) -> np.ndarray: + descriptors_list = list(descriptors) + rel_train_set = [single_descr for single_descr in descriptors_list] rel_train_set.append(self.T_descr) self.rel_index.build_index(rel_train_set) - RI_scores=self.rel_index.rank(*self.ADJs) + RI_scores = self.rel_index.rank(*self.ADJs) diff = np.ones(len(descriptors_list)) base_RI = RI_scores[rel_train_set[-1]] for i in range(len(descriptors_list)): - diff[i]= RI_scores[rel_train_set[i]] - base_RI + diff[i] = RI_scores[rel_train_set[i]] - base_RI return diff -class Logit_ImageSaliencyAugmenter(ImageSaliencyAugmenter): - """ - Robust Augmenter that yields a number of augmentations - of an input image, as well as preserved-area masks, - used for use in saliency map generation. +class LogitImageSaliencyAugmenter(ImageSaliencyAugmenter): """ + Robust Augmenter that yields a number of augmentations + of an input image, as well as preserved-area masks + used in saliency map generation. - def __init__(self, window_size=50, stride=20): - """ - :param int window_size: the block window size - (with value 0, other areas with value 1) - :param int stride: the sliding step - """ + :param window_size: the block window size (with value 0, other areas + with value 1) + :param stride: the sliding step + """ + def __init__(self, window_size: int = 50, stride: int = 20): self.window_size = window_size self.stride = stride - self.masks = self.generate_block_masks(window_size=window_size,\ - stride=stride) - - @classmethod - def is_usable(cls): - """ - Check whether this implementation is available for use. - Required valid to be True - :return: - Boolean determination of whether this implementation is usable. - :rtype: bool - """ - - valid = True - return valid - - @classmethod - def get_config(self): - """ - Return a JSON-compliant dictionary that could be passed to - this class's ``from_config`` method to produce an - instance with identical configuration. - In the common case, this involves naming the keys of the dictionary - based on the initialization argument names as if it were to be passed - to the constructor via dictionary expansion. - :return: JSON type compliant configuration dictionary. - :rtype: dict - """ + self.masks = self.generate_block_masks( + window_size=window_size, + stride=stride + ) + def get_config(self) -> Dict[str, Any]: return { 'window_size': self.window_size, 'stride': self.stride, } - def generate_block_masks(self, window_size, stride, image_size=(224,224)): + def generate_block_masks( + self, + window_size: int, + stride: int, + image_size: Tuple[int, int] = (224, 224) + ) -> np.ndarray: """ - Generates sliding window type binary masks used in augment() to - mask an image. The Images are resized to 224x224 to - enable re-use of masks Generating the sliding window style masks. - :param int window_size: the block window size - (with value 0, other areas with value 1) - :param int stride: the sliding step - :param tuple image_size: the mask size which should be the - same to the image size + Generates sliding window type binary masks used in augment() to + mask an image. The Images are resized to 224x224 to + enable re-use of masks. + + :param window_size: the block window size (with value 0, other areas + with value 1) + :param stride: the sliding step + :param image_size: the mask size which should be the same to the image + size + :return: the sliding window style masks - :rtype: numpy.ndarray """ - rows = np.arange(0 + stride - window_size, image_size[0], stride) cols = np.arange(0 + stride - window_size, image_size[1], stride) mask_num = len(rows) * len(cols) - self._log.debug('mask_num: {}'.format(mask_num)) - masks = np.ones((mask_num, image_size[0], image_size[1])\ - , dtype=np.float64) + LOG.debug('mask_num: {}'.format(mask_num)) + masks = np.ones((mask_num, image_size[0], image_size[1]), + dtype=np.float64) i = 0 for r in rows: for c in cols: - if r<0: + if r < 0: r1 = 0 else: r1 = r @@ -205,7 +159,7 @@ def generate_block_masks(self, window_size, stride, image_size=(224,224)): r2 = image_size[0] else: r2 = r + window_size - if c<0: + if c < 0: c1 = 0 else: c1 = c @@ -219,40 +173,10 @@ def generate_block_masks(self, window_size, stride, image_size=(224,224)): masks = masks.reshape(mask_shape) return masks - def generate_masked_imgs(self, masks, img): - """ - Apply the masks onto one input image - :param numpy.ndarray masks: sliding window type masks in - [1, Height, Weight, 1] format. - :param numpy.ndarray img: Original base image - :return: List masked images - :rtype: List of PIL Images - """ - - masked_imgs = [] - for mask in masks: - masked_img = np.multiply(mask, img, casting='unsafe') - masked_imgs.append(Image.fromarray(np.uint8(masked_img))) - return masked_imgs - - def augment(self, image_mat): - """ - Takes in an image matrix and returns its augmented version - :param numpy.ndarray image_mat: - Image matrix to be augmented. - :return: A numpy arrays of augmented image matrices as well as masks - that indicate the regions in the augmented images that are - unmodified with respect to the input image (preserved regions). - Returned augmented images should be in the dimension format - [index, height, width [,channel]] with the the same data type as - the input image matrix. - Returned masks should be in the dimension format - [index, height, width,channel] with the boolean data type. - :rtype: (PIL.Image, numpy.ndarray) - """ - - masked_images = self.generate_masked_imgs(self.masks, image_mat) - return (masked_images, self.masks) - -SALIENCY_BLACKBOX_CLASS = Logit_SaliencyBlackbox -IMG_SALIENCY_AUGMENTER_CLASS = Logit_ImageSaliencyAugmenter + def augment( + self, + image_mat: np.ndarray + ) -> Tuple[Sequence[PIL.Image.Image], np.ndarray]: + img = PIL.Image.fromarray(image_mat) + masked_images = generate_masked_images(self.masks, img) + return masked_images, self.masks diff --git a/xaitk_saliency/impls/saliency/rise.py b/xaitk_saliency/impls/saliency/rise.py index 2b0dae3a..4e161155 100644 --- a/xaitk_saliency/impls/saliency/rise.py +++ b/xaitk_saliency/impls/saliency/rise.py @@ -1,14 +1,22 @@ -from ._interface import ImageSaliencyAugmenter, ImageSaliencyMapGenerator +import io +from typing import Any, Dict, Generator, Sequence, Tuple -import six import numpy as np +import PIL.Image from skimage.transform import resize -from smqtk.representation.data_element.memory_element import DataMemoryElement +from smqtk_dataprovider.impls.data_element.memory import DataMemoryElement +from smqtk_descriptors import DescriptorGenerator + +from xaitk_saliency.interfaces.saliency import ( + ImageSaliencyAugmenter, + ImageSaliencyMapGenerator, + SaliencyBlackbox, +) class RISEAugmenter (ImageSaliencyAugmenter): - def __init__(self, N, s, p1, input_size): + def __init__(self, N: int, s: int, p1: float, input_size: Tuple[int, int]): """ Generate a set of random masks to apply to the image. :param int N: @@ -22,7 +30,6 @@ def __init__(self, N, s, p1, input_size): Size of the model's input. Smaller masks are upsampled to this resolution to be applied to (multiplied with) the input later. E.g. (224, 224) """ - self.N = N self.s = s self.p1 = p1 @@ -30,8 +37,8 @@ def __init__(self, N, s, p1, input_size): cell_size = np.ceil(np.array(input_size) / s) # Upscale factor up_size = (s + 1) * cell_size - - # Generate a set of random grids of small resolution + + # Generate a set of random grids of small resolution grid = np.random.rand(N, s, s) < p1 grid = grid.astype('float32') @@ -44,35 +51,11 @@ def __init__(self, N, s, p1, input_size): # Linear upsampling and random cropping masks[i, :, :] = resize(grid[i], up_size, order=1, mode='reflect', anti_aliasing=False)[x:x + input_size[0], y:y + input_size[1]] + # Reshape brings this to: (N, input_size[0], input_size[1], 1) self.masks = masks.reshape(-1, *input_size, 1) self.input_size = input_size - - @classmethod - def is_usable(cls): - """ - Check whether this implementation is available for use. - Required valid presence of the six and numpy libraries - :return: - Boolean determination of whether this implementation is usable. - :rtype: bool - """ - - return six and np - - - def get_config(self): - """ - Return a JSON-compliant dictionary that could be passed to this - class's ``from_config`` method to produce an instance with identical - configuration. - In the common case, this involves naming the keys of the dictionary - based on the initialization argument names as if it were to be passed - to the constructor via dictionary expansion. - :return: JSON type compliant configuration dictionary. - :rtype: dict - """ - + def get_config(self) -> Dict[str, Any]: return { 'N': self.N, 's': self.s, @@ -80,24 +63,10 @@ def get_config(self): 'input_size': self.input_size, } - def augment(self, image_mat): - """ - :param numpy.ndarray image_mat: - Image matrix to be augmented. - - :return: A numpy arrays of augmented image matrices as well as masks - that indicate the regions in the augmented images that are - unmodified with respect to the input image (preserved regions). - - Returned augmented images should be in the dimension format - [index, height, width [,channel]] with the the same data type as - the input image matrix. - - Returned masks should be in the dimension format - [index, height, width] with the boolean data type. - :rtype: (numpy.ndarray, numpy.ndarray) - """ - + def augment( + self, + image_mat: np.ndarray + ) -> Tuple[Sequence[PIL.Image.Image], np.ndarray]: # If image is grayscale if len(image_mat.shape) == 2: image_mat = np.expand_dims(image_mat, 2).repeat(3, axis=2) @@ -106,104 +75,53 @@ def augment(self, image_mat): class RISEGenerator (ImageSaliencyMapGenerator): - def __init__(self, input_size): + def __init__(self, input_size: Tuple[int, int]): """ Interface for randomized input sampling based explanations for blackbox models https://arxiv.org/abs/1806.07421 """ - self.org_hw = input_size - - @classmethod - def is_usable(cls): - """ - Check whether this implementation is available for use. - Required valid presence of query image feature - and base image descriptor - :return: - Boolean determination of whether this implementation is usable. - :rtype: bool - """ - - return resize and np - - - def get_config(self): - """ - Return a JSON-compliant dictionary that could be passed to this - class's ``from_config`` method to produce an instance with identical - configuration. - In the common case, this involves naming the keys of the dictionary - based on the initialization argument names as if it were to be passed - to the constructor via dictionary expansion. - :return: JSON type compliant configuration dictionary. - :rtype: dict - """ - + def get_config(self) -> Dict[str, Any]: return { 'input_size': self.org_hw } - - def generate(self, image_mat, augmenter, descriptor_generator, - blackbox): - """ - Generate an image saliency heat-map matrix given a blackbox's behavior - over the descriptions of an augmented base image. - - :param numpy.ndarray image_mat: - Numpy image matrix of the format [height, width [,channel]] that is - to be augmented. - - :param ImageSaliencyAugmenter augmenter: - Augmentation algorithm following - the :py:class:`ImageSaliencyAugmenter` interface. - - :param smqtk.algorithms.DescriptorGenerator descriptor_generator: - A descriptor generation algorithm following - the :py:class:`smqtk.algorithms.DescriptorGenerator` interface. - - :param SaliencyBlackbox blackbox: - Blackbox algorithm implementation following - the :py:class:`SaliencyBlackbox` interface. - - :return: A :py:class:`numpy.ndarray` matrix of the same [height, width] - shape as the input image matrix but of floating-point type within - the range of [0,1], where areas of higher value represent more - salient regions according to the given blackbox algorithm. - :rtype: numpy.ndarray[float] - """ - + def generate( + self, + image_mat: np.ndarray, + augmenter: ImageSaliencyAugmenter, + descriptor_generator: DescriptorGenerator, + blackbox: SaliencyBlackbox + ) -> PIL.Image.Image: resized_img = resize(image_mat, self.org_hw, order=1) masked_images, masks = augmenter.augment(resized_img) - - idx_to_uuid = [] - def iter_aug_img_data_elements(): + + def iter_aug_img_data_elements() -> Generator[DataMemoryElement, None, None]: for a in masked_images: - buff = six.BytesIO() - (a).save(buff, format="bmp") + buff = io.BytesIO() + a.save(buff, format="bmp") de = DataMemoryElement(buff.getvalue(), content_type='image/bmp') - idx_to_uuid.append(de.uuid()) yield de - uuid_to_desc = descriptor_generator.compute_descriptor_async(iter_aug_img_data_elements()) - scores = blackbox.transform((uuid_to_desc[uuid] for uuid in idx_to_uuid)) - + scores = blackbox.transform(descriptor_generator.generate_elements( + iter_aug_img_data_elements() + )) + # Compute a weighted average of masks w.r.t. the scores - saliency_map = np.average(masks, axis=0, weights=scores) + saliency_map: np.ndarray = np.average(masks, axis=0, weights=scores) saliency_map = np.squeeze(saliency_map) # Normalize saliency_map /= masks.mean(axis=0) # Resize back to the original image shape saliency_map = resize(saliency_map, image_mat.shape, order=1) - + # At this point the saliency map will be in some range [a, b], 0 <= a <= b <= 1. - # The absolute values characterize the average score of the masked image and + # The absolute values characterize the average score of the masked image and # therefore have some important information. However, for visualization purposes, # the saliency map can be rescaled to [0, 1]. # saliency_map = (saliency_map - saliency_map.min()) / (saliency_map.max() - saliency_map.min()) - + + # TODO: This is not returning currently a PIL Image. return saliency_map - diff --git a/xaitk_saliency/impls/saliency/sal_gen.py b/xaitk_saliency/impls/saliency/sal_gen.py index 0c1675ec..0fc5a040 100644 --- a/xaitk_saliency/impls/saliency/sal_gen.py +++ b/xaitk_saliency/impls/saliency/sal_gen.py @@ -1,58 +1,50 @@ -from tqdm import tqdm +import copy +import io +from typing import Any, Dict, Generator + +import cv2 from matplotlib import pyplot as plt -from datetime import datetime -import six import numpy as np -import PIL -import cv2 -import copy -from smqtk.algorithms.saliency import ImageSaliencyMapGenerator -from smqtk.algorithms.descriptor_generator import DescriptorGenerator -from smqtk.representation.data_element.file_element import DataFileElement -from smqtk.representation.data_element.memory_element \ - import DataMemoryElement -from skimage.transform import resize +import PIL.Image +from smqtk_dataprovider.impls.data_element.memory import DataMemoryElement +from smqtk_descriptors import DescriptorGenerator +from xaitk_saliency.interfaces.saliency import ( + ImageSaliencyAugmenter, + ImageSaliencyMapGenerator, + SaliencyBlackbox, +) -class Logit_ImageSaliencyMapGenerator(ImageSaliencyMapGenerator): + +class LogitImageSaliencyMapGenerator(ImageSaliencyMapGenerator): """ Interface for the method of generation of a saliency map given an image augmentation and blackbox algorithms. """ - def __init__(self,threshold=0.2): - + def __init__(self, threshold: float = 0.2): self.thresh = threshold - self.org_hw = None - - def get_config(self): - + def get_config(self) -> Dict[str, Any]: return { 'threshold': self.thresh, } - @classmethod - def is_usable(cls): + def overlay_saliency_map( + self, + sa_map: np.ndarray, + org_img: np.ndarray + ) -> PIL.Image.Image: """ - Check whether this implementation is available for use. - Required valid presence of svm and svmutil modules - :return: - Boolean determination of whether this implementation is usable. - :rtype: bool - """ - return plt and np + Overlay the saliency map on top of original image - def overlay_saliency_map(self,sa_map, org_img): - """ - overlay the saliency map on top of original image - :param numpy.array sa_map: saliency map - :param numpy.array org_img: Original image - :return: Overlayed image - :rtype: PIL.Image + :param sa_map: saliency map + :param org_img: Original image + + :return: Overlaid image """ plt.switch_backend('agg') - height = float(self.org_hw[0]) - width = float(self.org_hw[1]) + height = float(org_img.shape[0]) + width = float(org_img.shape[1]) fig = plt.figure(dpi=int(height)) fig.set_size_inches((width / height), 1, forward=False) ax = plt.Axes(fig, [0., 0., 1., 1.]) @@ -61,72 +53,64 @@ def overlay_saliency_map(self,sa_map, org_img): ax.imshow(org_img) ax.imshow(sa_map, cmap='jet', alpha=0.5) fig.canvas.draw() - np_data = np.fromstring(fig.canvas.tostring_rgb(), \ - dtype=np.uint8, sep='') + np_data = np.fromstring(fig.canvas.tostring_rgb(), + dtype=np.uint8, sep='') np_data = np_data.reshape(fig.canvas.get_width_height()[::-1] + (3,)) im = PIL.Image.fromarray(np_data) plt.close() return im - def weighted_avg(self,scalar_vec,masks): - masks = masks.reshape(-1,224,224,1) - cur_filters = copy.deepcopy(masks[:,:,:,0]) + def weighted_avg(self, scalar_vec: np.ndarray, masks: np.ndarray) -> np.ndarray: + """ + :param scalar_vec: Array of floats. + :param masks: Array of image masks congruent in first-dim size with + `scalar_vec`. + :return: Weighted saliency mask in floating point. + """ + masks = masks.reshape(-1, 224, 224, 1) + cur_filters = copy.deepcopy(masks[:, :, :, 0]) count = masks.shape[0] - np.sum(cur_filters, axis=0) for i in range(len(cur_filters)): - cur_filters[i] = (1.0 - cur_filters[i]) * np.clip(scalar_vec[i], \ - a_min=0.0, a_max=None) + cur_filters[i] = (1.0 - cur_filters[i]) * np.clip(scalar_vec[i], + a_min=0.0, a_max=None) res_sa = np.sum(cur_filters, axis=0) / count - res_sa = np.clip(res_sa, a_min=((np.max(res_sa)) * self.thresh), \ - a_max = None) + res_sa = np.clip(res_sa, + a_min=((np.max(res_sa)) * self.thresh), + a_max=None) return res_sa - def generate(self, base_image, augmenter, descriptor_generator, - blackbox): - """ - Generate an image saliency heat-map matrix given a blackbox's behavior - over the descriptions of an augmented base image. - :param numpy.ndarray base_image: - Numpy matrix of the format [height, width [,channel]] that is - to be augmented. - :param ImageSaliencyAugmenter augmenter: - Augmentation algorithm following - the :py:class:`ImageSaliencyAugmenter` interface. - :param smqtk.algorithms.DescriptorGenerator descriptor_generator: - A descriptor generation algorithm following - the :py:class:`smqtk.algorithms.DescriptorGenerator` interface. - :param SaliencyBlackbox blackbox: - Blackbox algorithm implementation following - the :py:class:`SaliencyBlackbox` interface. - :return: A :py:class:`PIL.Image` of the same [height, width] - shape as the input image matrix but of floating-point type within - the range of [0,1], where areas of higher value represent more - salient regions according to the given blackbox algorithm. - :rtype: PIL.Image - """ + def generate( + self, + image_mat: np.ndarray, + augmenter: ImageSaliencyAugmenter, + descriptor_generator: DescriptorGenerator, + blackbox: SaliencyBlackbox + ) -> PIL.Image.Image: + # [width, height] shape of the input image matrix + org_wh = tuple(image_mat.shape[:2][::-1]) + + image_mat_resized = cv2.resize(image_mat, + (224, 224), + interpolation=cv2.INTER_LINEAR) + augs, masks = augmenter.augment(image_mat_resized) - self.org_hw = np.shape(base_image)[0:2] - base_image_resized = cv2.resize(base_image,(224,224) \ - ,interpolation=cv2.INTER_LINEAR) - augs, masks = augmenter.augment(base_image_resized) - idx_to_uuid = [] - def iter_aug_img_data_elements(): + def iter_aug_img_data_elements() -> Generator[DataMemoryElement, None, None]: for a in augs: - buff = six.BytesIO() - (a).save(buff, format="bmp") - de = DataMemoryElement(buff.getvalue(), - content_type='image/bmp') - idx_to_uuid.append(de.uuid()) - yield de - uuid_to_desc = descriptor_generator.compute_descriptor_async \ - (iter_aug_img_data_elements()) - scalar_vec = blackbox.transform( \ - (uuid_to_desc[uuid] for uuid in idx_to_uuid)) - final_sal_map = self.weighted_avg(scalar_vec,masks) - final_sal_map = cv2.resize(final_sal_map, \ - (self.org_hw[1], self.org_hw[0]),interpolation=cv2.INTER_LINEAR) - sal_map_ret = self.overlay_saliency_map(final_sal_map,base_image) - return sal_map_ret + buff = io.BytesIO() + a.save(buff, format="bmp") + de = DataMemoryElement(buff.getvalue(), + content_type='image/bmp') + yield de -IMG_SALIENCY_GENERATOR_CLASS=Logit_ImageSaliencyMapGenerator + scalar_vec = blackbox.transform(descriptor_generator.generate_elements( + iter_aug_img_data_elements() + )) + final_sal_map = self.weighted_avg(scalar_vec, masks) + final_sal_map = cv2.resize( + final_sal_map, org_wh, + interpolation=cv2.INTER_LINEAR + ) + sal_map_ret = self.overlay_saliency_map(final_sal_map, image_mat) + return sal_map_ret diff --git a/xaitk_saliency/impls/saliency/sbsm.py b/xaitk_saliency/impls/saliency/sbsm.py index 20532432..2a1c203b 100644 --- a/xaitk_saliency/impls/saliency/sbsm.py +++ b/xaitk_saliency/impls/saliency/sbsm.py @@ -1,236 +1,164 @@ -from PIL import Image +import io +import logging import os +from typing import Any, Dict, Iterable, Sequence, Tuple, Type + +import numpy as np +import PIL.Image from sklearn.metrics.pairwise import euclidean_distances -import six -from smqtk.algorithms import SmqtkAlgorithm -from smqtk.algorithms.saliency import SaliencyBlackbox,ImageSaliencyAugmenter -from smqtk.representation.data_element.memory_element import DataMemoryElement - -try: - import numpy as np - from tqdm import tqdm - import copy -except ImportError as ex: - logging.getLogger(__name__).warning("Failed to import numpy/tqdm \ - /copy module: %s", str(ex)) - np = None - tqdm = None - copy = None - - -class SBSM_SaliencyBlackbox (SaliencyBlackbox): +from smqtk_dataprovider.impls.data_element.memory import DataMemoryElement +from smqtk_descriptors import DescriptorElement, DescriptorGenerator +from tqdm import tqdm + +from xaitk_saliency.interfaces.saliency import ( + ImageSaliencyAugmenter, + SaliencyBlackbox, +) +from xaitk_saliency.utils.masking import generate_masked_images + + +LOG = logging.getLogger(__name__) + + +class SBSMSaliencyBlackbox(SaliencyBlackbox): """ - SBSM_SaliencyBlackbox function that yields some floating point - scalar value for a given masked base image descriptor element that + SBSM_SaliencyBlackbox function that yields some floating point + scalar value for a given masked base image descriptor element that signifies the proximity between the query image and masked image descriptors, used by class implementations of 'ImageSaliencyMapGenerator'. - """ - def __init__(self, query_f, base_descr): - """ - :param smqtk.representation.DescriptorElement query_f: - Feature of query image. - :param smqtk.representation.DescriptorElement base_descr: - Base image descriptor - """ + :param query_f: Feature of query image. + :param base_descr: Base image descriptor + """ - self.query_f = query_f + def __init__(self, query_f: np.ndarray, base_descr: np.ndarray): + self.query_f = query_f self.base_descr = base_descr @classmethod - def is_usable(cls): - """ - Check whether this implementation is available for use. - Required valid presence of query image feature - and base image descriptor - :return: - Boolean determination of whether this implementation is usable. - :rtype: bool - """ - - return True - - @classmethod - def from_iqr_session(cls, iqrs, descr_gen, base_image): - """ - Create an ``SaliencyBlackbox`` instance from iqrs session, - descriptor generator and base_image. - :param smqtk.iqr.IqrSession iqrs:`smqtk.iqr.IqrSession` instance. - :param smqtk.algorithms.DescriptorGenerator descr_gen: - The descriptor generator used by smqtk. - :param PIL.Image base_image: The Base image for which we need to - calculate a saliency map. - :return: A new instance of a class implementing the - ``SaliencyBlackbox`` class. - :rtype: SaliencyBlackbox - """ - + def from_iqr_session( + cls: Type["SaliencyBlackbox"], + iqrs: Any, # "IqrSession" + descr_generator: DescriptorGenerator, + base_image: PIL.Image.Image + ) -> "SaliencyBlackbox": assert iqrs if (len(iqrs.external_positive_descriptors)) != 1: raise ValueError("Saliency generation in class``{}`` " - "only supports one query sample, but" - "recieved more than 1 external positive." - .format(cls.__name__)) - query_f = [ext_pos.vector() for ext_pos in \ - iqrs.external_positive_descriptors] - buff = six.BytesIO() + "only supports one query sample, but" + "recieved more than 1 external positive." + .format(cls.__name__)) + # Implementation only ever considers the first query descriptor, so + # we reflect such a selection here based on the previous incorrect + # impl. + # query_f = [ext_pos.vector() for ext_pos in \ + # iqrs.external_positive_descriptors] + query_f = list(iqrs.external_positive_descriptors)[0].vector() + buff = io.BytesIO() base_image.save(buff, format="bmp") de = DataMemoryElement(buff.getvalue(), content_type='image/bmp') - base_descr = descr_gen.compute_descriptor(de) - return SBSM_SaliencyBlackbox(query_f, base_descr) - - def get_config(self): - """ - Return a JSON-compliant dictionary that could be passed to this - class's ``from_config`` method to produce an instance with identical - configuration. - In the common case, this involves naming the keys of the dictionary - based on the initialization argument names as if it were to be passed - to the constructor via dictionary expansion. - :return: JSON type compliant configuration dictionary. - :rtype: dict - """ + base_descr = descr_generator.generate_one_element(de) + base_descr_vec = base_descr.vector() + assert base_descr_vec is not None # we literally just generated it... + return SBSMSaliencyBlackbox(query_f, base_descr_vec) + def get_config(self) -> Dict[str, Any]: return { 'query_f': self.query_f, 'base_descr': self.base_descr, } - def transform(self, descriptors): - """ - Transform some descriptor element into a saliency scalar. - :param collections.Iterable[smqtk.representation.DescriptorElement] - descriptors: Descriptor to get the saliency of. - :return: The saliency value for the given descriptor. - :rtype: numpy.ndarray[float] - """ - - org_dis = abs(euclidean_distances(self.query_f[0].reshape(1, -1) - ,(self.base_descr.vector()).reshape(1, -1))) + def transform(self, descriptors: Iterable[DescriptorElement]) -> np.ndarray: + org_dis = abs(euclidean_distances(self.query_f.reshape(1, -1), + self.base_descr.reshape(1, -1))) descriptors_list = list(descriptors) + vector_list = [d.vector() for d in descriptors_list] + if None in vector_list: + raise RuntimeError("One or more input descriptor elements did not " + "refer to an actual vector (some None-valued).") diff = np.ones(len(descriptors_list)) query_f_reshaped = self.query_f[0].reshape(1, -1) - for i in range(len(descriptors_list)): - diff[i] = max(abs(euclidean_distances \ - (descriptors_list[i].vector().reshape(1, -1), \ - query_f_reshaped))-org_dis,0) + for i in range(len(vector_list)): + v = vector_list[i] + assert v is not None + diff[i] = max(abs(euclidean_distances( + v.reshape(1, -1), + query_f_reshaped + )) - org_dis, 0) return diff -class SBSM_ImageSaliencyAugmenter (ImageSaliencyAugmenter): +class SBSMImageSaliencyAugmenter(ImageSaliencyAugmenter): """ Algorithm that yields a number of augmentations of an input image, as well as preserved-area masks, used for use in saliency map generation. - For a more robust augmenter that can work for different image_sizes + For a more robust augmenter that can work for different image_sizes use implementation class 'Logit_ImageSaliencyAugmenter'. - """ - def __init__(self, window_size=20, stride=4): - """ - :param int window_size: the block window size - (with value 0, other areas with value 1) - :param int stride: the sliding step - """ + :param window_size: the block window size (with value 0, other areas + with value 1) + :param stride: the sliding step + """ + def __init__(self, window_size: int = 20, stride: int = 4): self.window_size = window_size self.stride = stride - self.masks = self.generate_block_masks(self.window_size,self.stride) - - @classmethod - def is_usable(cls): - """ - Check whether this implementation is available for use. - Required valid presence of tqdm and copy modules - :return: - Boolean determination of whether this implementation is usable. - :rtype: bool - """ - - valid = (tqdm is not None) and (copy is not None) - if not valid: - cls.get_logger().debug("tqdm or copy python \ - module cannot be imported") - return valid - - def get_config(self): - """ - Return a JSON-compliant dictionary that could be passed to - this class's ``from_config`` method to produce an instance - with identical configuration. - In the common case, this involves naming the keys of the dictionary - based on the initialization argument names as if it were to be passed - to the constructor via dictionary expansion. - :return: JSON type compliant configuration dictionary. - :rtype: dict - """ + self.masks = self.generate_block_masks(self.window_size, self.stride) + def get_config(self) -> Dict[str, Any]: return { 'window_size': self.window_size, 'stride': self.stride, } - def generate_block_masks(self, window_size, stride, image_size=(224, 224)): + def generate_block_masks( + self, + window_size: int, + stride: int, + image_size: Tuple[int, int] = (224, 224) + ) -> np.ndarray: """ - Generates sliding window type binary masks used in augment() to mask - an image. The Images are resized to 224x224 to enable re-use of masks - Generating the sliding window style masks - :param int window_size: the block window size - (with value 0, other areas with value 1) - :param int stride: the sliding step - :param tuple(default: (224, 224)) image_size:The mask size which should - be the same to the image size. + Generates sliding window type binary masks used in augment() to + mask an image. The Images are resized to 224x224 to + enable re-use of masks. + + :param window_size: the block window size (with value 0, other areas + with value 1) + :param stride: the sliding step + :param image_size: the mask size which should be the same to the image + size + :return: the sliding window style masks - :rtype: numpy.ndarray """ - - - #The augmenter only supports certain factors of window_size and stride - #for a more robust augmenter use Logit_ImageSaliencyAugmenter - if (image_size[0]-window_size)%stride != 0: + # The augmenter only supports certain factors of window_size and stride + # for a more robust augmenter use LogitImageSaliencyAugmenter + if (image_size[0] - window_size) % stride != 0: raise ValueError("Change window size and stride to satisfy " - "condition:(image_height-window_size)%stride=0") + "condition:(image_height-window_size)%stride=0") if not os.path.isfile('block_mask_{}_{}.npy'.format(window_size, stride)): - grid_num_r = ((image_size[0] - window_size) // stride)+1 - grid_num_c = ((image_size[1] - window_size) // stride)+1 + grid_num_r = ((image_size[0] - window_size) // stride) + 1 + grid_num_c = ((image_size[1] - window_size) // stride) + 1 mask_num = grid_num_r * grid_num_c masks = np.ones((mask_num, image_size[0], image_size[1]), dtype=np.float32) i = 0 - for r in tqdm(np.arange(0, image_size[0] - window_size+1, stride), total=grid_num_r, desc="Generating rows"): - for c in np.arange(0, image_size[1] - window_size+1, stride): + for r in tqdm(np.arange(0, image_size[0] - window_size + 1, stride), total=grid_num_r, + desc="Generating rows"): + for c in np.arange(0, image_size[1] - window_size + 1, stride): masks[i, r:r + window_size, c:c + window_size] = 0.0 i += 1 masks = masks.reshape(-1, *image_size) masks.tofile('block_mask_{}_{}.npy'.format(window_size, stride)) else: - self._log.debug("Loading masks from file block_mask_{}_{}.npy".format(window_size, stride)) + LOG.debug("Loading masks from file block_mask_{}_{}.npy".format(window_size, stride)) masks = np.fromfile('block_mask_{}_{}.npy'.format(window_size, stride), - dtype=np.float32).reshape(-1, *image_size) + dtype=np.float32).reshape(-1, *image_size) return masks - def generate_masked_imgs(self, masks, img): - """ - Apply the masks onto one input image - :param numpy.ndarray masks: sliding window type - masks in [1, Height, Weight, 1] format. - :param numpy.ndarray img: Original base image - :return: List masked images - :rtype: list[PIL.Image] - """ - - if (img.ndim == 2): - channels = 1 - if (img.ndim == 3): - channels = 3 - masked_imgs = [] - masked_img = copy.deepcopy(img) - for cnt,mask in enumerate(masks): - for ind in range(channels): - masked_img[:,:,ind] = np.multiply(mask, img[:,:,ind]) - masked_imgs.append(Image.fromarray(np.uint8(masked_img))) - return masked_imgs - - def augment(self, image_mat): + def augment( + self, + image_mat: np.ndarray + ) -> Tuple[Sequence[PIL.Image.Image], np.ndarray]: """ Takes in an image matrix and returns its augmented version :param numpy.ndarray image_mat: @@ -245,9 +173,6 @@ def augment(self, image_mat): [index, height, width,channel] with the boolean data type. :rtype: (PIL.Image, numpy.ndarray) """ - - masked_images = self.generate_masked_imgs(self.masks,image_mat) - return (masked_images,self.masks) - -SALIENCY_BLACKBOX_CLASS=SBSM_SaliencyBlackbox -IMG_SALIENCY_AUGMENTER_CLASS=SBSM_ImageSaliencyAugmenter + img = PIL.Image.fromarray(image_mat) + masked_images = generate_masked_images(self.masks, img) + return masked_images, self.masks diff --git a/xaitk_saliency/interfaces/saliency.py b/xaitk_saliency/interfaces/saliency.py index 57fe02c0..ebdbcccb 100644 --- a/xaitk_saliency/interfaces/saliency.py +++ b/xaitk_saliency/interfaces/saliency.py @@ -1,17 +1,28 @@ import abc +from typing import Any, Iterable, Sequence, Tuple, Type, TypeVar -from smqtk.algorithms import SmqtkAlgorithm -from smqtk.representation.data_element.memory_element import DataMemoryElement +import numpy as np +import PIL.Image +from smqtk_core import Plugfigurable +from smqtk_descriptors import DescriptorElement, DescriptorGenerator -class SaliencyBlackbox (SmqtkAlgorithm): +T = TypeVar("T", bound="SaliencyBlackbox") + + +class SaliencyBlackbox (Plugfigurable): """ Blackbox function that produces some floating point scalar value for a given descriptor element. """ @classmethod - def from_iqr_session(cls, iqrs, descr_generator, base_elem): + def from_iqr_session( + cls: Type["SaliencyBlackbox"], + iqrs: Any, # "IqrSession" + descr_generator: DescriptorGenerator, + base_image: PIL.Image.Image + ) -> "SaliencyBlackbox": """ Create an ``SaliencyBlackbox`` instance from an :class:`smqtk.iqr.IqrSession` instance. @@ -28,91 +39,97 @@ def from_iqr_session(cls, iqrs, descr_generator, base_elem): if a blackbox instance. :param descr_generator: Descriptor generator instance. - :param base_elem: - A :py:class:`smqtk.representation.DataElement` representing the - data over which a saliency map may be generated. + :param base_image: + A :py:class:`PIL.Image.Image` over which a saliency map may be + generated. :return: A new instance of of a class implementing the ``SaliencyBlackbox`` class. - :rtype: SaliencyBlackbox - """ raise NotImplementedError("The ``from_iqr_session`` classmethod is " "not implemented for class ``{}``." .format(cls.__name__)) @abc.abstractmethod - def transform(self, descriptors): + def transform(self, descriptors: Iterable[DescriptorElement]) -> np.ndarray: """ Transform some number of descriptor elements into a saliency scalar values. - :param collections.Iterable[smqtk.representation.DescriptorElement] descriptors: + :param descriptors: Descriptors to get the saliency values of. - :return: The saliency value for the given descriptor. - :rtype: numpy.ndarray[float] + :return: The floating-point saliency value for the given descriptor. """ -class ImageSaliencyAugmenter (SmqtkAlgorithm): +class ImageSaliencyAugmenter (Plugfigurable): """ Algorithm that yields a number of augmentations of an input image, as well as preserved-area masks, used for use in saliency map generation. """ @abc.abstractmethod - def augment(self, image_mat): + def augment( + self, + image_mat: np.ndarray + ) -> Tuple[Sequence[PIL.Image.Image], np.ndarray]: """ - :param numpy.ndarray image_mat: - Image matrix to be augmented. + Takes in an image matrix and returns its augmented version + + :param image_mat: + Image matrix to be augmented. This should be in the format + [height, width [,channel]]. - :return: A numpy arrays of augmented image matrices as well as masks - that indicate the regions in the augmented images that are - unmodified with respect to the input image (preserved regions). + :return: A sequence of augmented images as well as congruently sized + array of masks that indicate the regions in the augmented images + that are unmodified with respect to the input image (preserved + regions). - Returned augmented images should be in the dimension format - [index, height, width [,channel]] with the the same data type as - the input image matrix. + Returned augmented images should have the same height, width and + channel format as the input image matrix. Returned masks should be in the dimension format [index, height, width] with the boolean data type. - :rtype: (PIL.Image, numpy.ndarray) """ -class ImageSaliencyMapGenerator (SmqtkAlgorithm): +class ImageSaliencyMapGenerator (Plugfigurable): """ Interface for the method of generation of a saliency map given an image augmentation and blackbox algorithms. """ @abc.abstractmethod - def generate(self, image_mat, augmenter, descriptor_generator, - blackbox): + def generate( + self, + image_mat: np.ndarray, + augmenter: ImageSaliencyAugmenter, + descriptor_generator: DescriptorGenerator, + blackbox: SaliencyBlackbox + ) -> PIL.Image.Image: """ Generate an image saliency heat-map matrix given a blackbox's behavior over the descriptions of an augmented base image. - :param numpy.ndarray image_mat: + :param image_mat: Numpy image matrix of the format [height, width [,channel]] that is to be augmented. - - :param ImageSaliencyAugmenter augmenter: + :param augmenter: Augmentation algorithm following the :py:class:`ImageSaliencyAugmenter` interface. - - :param smqtk.algorithms.DescriptorGenerator descriptor_generator: + :param descriptor_generator: A descriptor generation algorithm following the :py:class:`smqtk.algorithms.DescriptorGenerator` interface. - - :param SaliencyBlackbox blackbox: + :param blackbox: Blackbox algorithm implementation following the :py:class:`SaliencyBlackbox` interface. - :return: A :py:class:`numpy.ndarray` matrix of the same [height, width] + :return: A PIL Image with the same height and width properties as the + input image. + + :py:class:`numpy.ndarray` matrix of the same [height, width] shape as the input image matrix but of floating-point type within the range of [0,1], where areas of higher value represent more salient regions according to the given blackbox algorithm. - :rtype: numpy.ndarray[float] """ diff --git a/xaitk_saliency/py.typed b/xaitk_saliency/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/xaitk_saliency/utils/__init__.py b/xaitk_saliency/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/xaitk_saliency/utils/masking.py b/xaitk_saliency/utils/masking.py new file mode 100644 index 00000000..4a1acea7 --- /dev/null +++ b/xaitk_saliency/utils/masking.py @@ -0,0 +1,30 @@ +from typing import List + +import numpy as np +import PIL.Image + + +def generate_masked_images( + masks: np.ndarray, + img: PIL.Image.Image +) -> List[PIL.Image.Image]: + """ + Apply some binary masks onto one common image, generating a number of new + images with the masked regions maintained. + + We expect the "mask" matrices and the image to be the same height and + width, and be valued in the [0, 1] floating-point range. + In the mask matrix, higher values correspond to regions of the image that + preserved. E.g. a 0 in the mask will translate to blacking out the + corresponding location in the source image. + + :param masks: Mask images in the [N, Height, Weight, 1] shape format. + :param img: Original base image + + :return: List of masked images in PIL Image form. + """ + masked_imgs = [] + for mask in masks: + masked_img = np.multiply(mask, img, casting='unsafe') + masked_imgs.append(PIL.Image.fromarray(np.uint8(masked_img))) + return masked_imgs