diff --git a/poetry.lock b/poetry.lock index f8e6a3e5..6820b96d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -142,16 +142,17 @@ test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock [[package]] name = "astar-utils" -version = "0.2.0b0" +version = "0.2.0b1" description = "Contains commonly-used utilities for AstarVienna's projects." optional = false python-versions = ">=3.9,<4.0" files = [ - {file = "astar_utils-0.2.0b0-py3-none-any.whl", hash = "sha256:4a47a188edb3156ee45e0bfdc211350c9553c3c69aad1ce9872bb0b2c9ec7044"}, - {file = "astar_utils-0.2.0b0.tar.gz", hash = "sha256:b45322ca89dff98e89828bfe0f1cd6a04fe3fafb5f49dc5a42ce1431328a8e5e"}, + {file = "astar_utils-0.2.0b1-py3-none-any.whl", hash = "sha256:b59aaeb5920333c528aa7bef07a04a009e9a1e668e84e3cfbc09fdd8dae7fdda"}, + {file = "astar_utils-0.2.0b1.tar.gz", hash = "sha256:f9a5fd493623c36d79f7df97f011e073fadddfe9ea94fe35d0abee82bbbf1a2b"}, ] [package.dependencies] +colorama = ">=0.4.6,<0.5.0" more-itertools = ">=10.1.0,<11.0.0" pyyaml = ">=6.0.1,<7.0.0" @@ -2235,70 +2236,88 @@ files = [ [[package]] name = "pillow" -version = "10.1.0" +version = "10.2.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, - {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, - {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "platformdirs" @@ -3712,4 +3731,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "db0d8ac00666a493c6c939f6b621d50c7a8b7c44cecd257adbc2671163b95f21" +content-hash = "14abc3786229999350744030ad20b0113c41a61a9495e96d9e002dc5fa046037" diff --git a/pyproject.toml b/pyproject.toml index 1e403a06..38164ddb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ tqdm = "^4.66.1" synphot = "^1.2.1" skycalc_ipy = "^0.3.0" anisocado = "^0.3.0" -astar-utils = {version = "^0.2.0b0", allow-prereleases = true} +astar-utils = {version = "^0.2.0b1", allow-prereleases = true} [tool.poetry.group.dev] optional = true @@ -90,6 +90,7 @@ filterwarnings = [ "ignore:divide by zero encountered in double_scalars:RuntimeWarning", "ignore:invalid value encountered in multiply:RuntimeWarning", "ignore:Cannot merge meta key.*:astropy.utils.metadata.MergeConflictWarning", + "default:The fov_grid*:DeprecationWarning", # Raised when saving fits files, not so important to fix: "ignore:.*a HIERARCH card will be created.*:astropy.io.fits.verify.VerifyWarning", # Web-related issues, fix at some point diff --git a/scopesim/__init__.py b/scopesim/__init__.py index 16fb1246..b30d4ec1 100644 --- a/scopesim/__init__.py +++ b/scopesim/__init__.py @@ -1,10 +1,9 @@ -"Generalised telescope observation simulator." +"""Generalised telescope observation simulator.""" ############################################################################### # TURN OFF WARNINGS # ############################################################################### -import sys -import logging + import warnings import yaml from importlib import metadata @@ -26,30 +25,12 @@ # SET BASIC LOGGING LEVEL # ############################################################################### -# TODO: this should be replaced with YAML-based config!! see prepipy - # This should be part of ScopeSim (the app) and not scopesim_core eventually +# TODO: need to add a function to reload the config! -top_logger = logging.getLogger("astar") -top_logger.setLevel(logging.WARNING) -sim_logger = top_logger.getChild(__package__) -sim_logger.setLevel(logging.DEBUG) -top_logger.propagate = False -formatter = logging.Formatter("%(name)s - %(levelname)s: %(message)s") - -log_dict = rc.__config__["!SIM.logging"] -if log_dict["log_to_file"]: - file_handler = logging.FileHandler(log_dict["file_path"], - log_dict["file_open_mode"]) - file_handler.setLevel(log_dict["file_level"]) # DEBUG - file_handler.setFormatter(formatter) - top_logger.addHandler(file_handler) - -if log_dict["log_to_console"]: - stdout_handler = logging.StreamHandler(sys.stdout) - stdout_handler.setLevel(log_dict["console_level"]) # INFO - stdout_handler.setFormatter(formatter) - top_logger.addHandler(stdout_handler) +# Import convenience functions +from .utils import update_logging, log_to_file, set_console_log_level +update_logging() ############################################################################### diff --git a/scopesim/defaults.yaml b/scopesim/defaults.yaml index 37a35758..e964d840 100644 --- a/scopesim/defaults.yaml +++ b/scopesim/defaults.yaml @@ -52,12 +52,51 @@ properties : preamble_file: None logging : - log_to_file: False - log_to_console: True - file_path: ".scopesim.log" - file_open_mode: "w" # w - overwrite, a - append - file_level: "DEBUG" # DEBUG INFO WARNING ERROR CRITICAL - console_level: "INFO" # DEBUG INFO WARNING ERROR CRITICAL + # This sub-dict enables direct configuration of logging. + # The corresponding schema can be found at: + # https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema + + version: 1 + disable_existing_loggers: False # Not sure what's best here?? + + root: # To allow e.g. warnings -> logging + level: INFO + handlers: [console] # [console, file] or just [console] + + loggers: + astar: + level: WARNING + handlers: [console] # [console, file] or just [console] + propagate: False # Any logging from astar stops here. + # Or don't add handlers here, but let it propagate to root? + # This doesn't work because NestedMapping doesn't like "." in keys... + # astar.scopesim: + # level: DEBUG + # propagate: True # Goes through to astar logger. + + handlers: + console: + class: logging.StreamHandler + level: INFO + formatter: color + stream: ext://sys.stdout + file: + class : logging.handlers.RotatingFileHandler + level: DEBUG + formatter: verbose + filename: ".scopesim.log" + mode: "w" # w - overwrite, a - append + encoding: "utf-8" + delay: True + maxBytes: 32768 + backupCount: 3 + + formatters: + verbose: + format: '%(asctime)s - %(levelname)-8s - %(name)s - %(funcName)s - %(message)s' + color: + '()': astar_utils.loggers.ColoredFormatter + show_name: True tests : # overridden in tests/__init__.py diff --git a/scopesim/effects/apertures.py b/scopesim/effects/apertures.py index 91484ed2..4da4270d 100644 --- a/scopesim/effects/apertures.py +++ b/scopesim/effects/apertures.py @@ -1,5 +1,6 @@ """Effects related to field masks, including spectroscopic slits.""" +import warnings import yaml import numpy as np @@ -127,7 +128,8 @@ def apply_to(self, obj, **kwargs): # Outdated. Remove when removing all old FOVManager code from effects def fov_grid(self, which="edges", **kwargs): """Return a header with the sky coordinates.""" - logger.warning("DetectorList.fov_grid will be depreciated in v1.0") + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) if which == "edges": self.meta.update(kwargs) return self.header @@ -206,16 +208,6 @@ def __init__(self, **kwargs): self.table = self.get_table(**kwargs) - # def fov_grid(self, which="edges", **kwargs): - # """ Returns a header with the sky coordinates """ - # if which == "edges": - # self.table = self.get_table(**kwargs) - # return self.header # from base class ApertureMask - # - # elif which == "masks": - # self.meta.update(kwargs) - # return self.mask - def get_table(self, **kwargs): self.meta.update(kwargs) x = from_currsys(self.meta["x"]) @@ -315,14 +307,6 @@ def apply_to(self, obj, **kwargs): return obj - # def fov_grid(self, which="edges", **kwargs): - # params = deepcopy(self.meta) - # params.update(kwargs) - # if which == "edges": - # return [ap.fov_grid(which=which, **params) for ap in self.apertures] - # if which == "masks": - # return {ap.meta["id"]: ap.mask for ap in self.apertures} - @property def apertures(self): return self.get_apertures(range(len(self.table))) @@ -459,6 +443,8 @@ def apply_to(self, obj, **kwargs): def fov_grid(self, which="edges", **kwargs): """See parent docstring.""" + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) return self.current_slit.fov_grid(which=which, **kwargs) def change_slit(self, slitname=None): diff --git a/scopesim/effects/detector_list.py b/scopesim/effects/detector_list.py index 1d13866b..71683c6f 100644 --- a/scopesim/effects/detector_list.py +++ b/scopesim/effects/detector_list.py @@ -1,5 +1,7 @@ """TBA.""" +import warnings + import numpy as np from astropy import units as u from astropy.table import Table @@ -158,7 +160,8 @@ def apply_to(self, obj, **kwargs): def fov_grid(self, which="edges", **kwargs): """Return an ApertureMask object. kwargs are "pixel_scale" [arcsec].""" - logger.warning("DetectorList.fov_grid will be depreciated in v1.0") + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) aperture_mask = None if which == "edges": self.meta.update(kwargs) diff --git a/scopesim/effects/electronic.py b/scopesim/effects/electronic.py index f40d9e07..dc43dac5 100644 --- a/scopesim/effects/electronic.py +++ b/scopesim/effects/electronic.py @@ -100,7 +100,7 @@ def apply_to(self, obj, **kwargs): from_currsys("!OBS.detector_readout_mode")) if isinstance(obj, ImagePlaneBase) and mode_name == "auto": mode_name = self.select_mode(obj, **kwargs) - print("Detector mode set to", mode_name) + logger.info("Detector mode set to %s", mode_name) self.meta["detector_readout_mode"] = mode_name props_dict = self.mode_properties[mode_name] @@ -195,10 +195,10 @@ def apply_to(self, obj, **kwargs): if exptime is None: exptime = from_currsys("!OBS.dit") * from_currsys("!OBS.ndit") - print(f"Requested exposure time: {exptime:.3f} s") + logger.info("Requested exposure time: %.3f s", exptime) if exptime < mindit: - print(f" increased to MINDIT: {mindit:.3f} s") + logger.info(" increased to MINDIT: %.3f s", mindit) exptime = mindit full_well = from_currsys(self.meta["full_well"]) @@ -215,12 +215,11 @@ def apply_to(self, obj, **kwargs): if dit < from_currsys(self.meta["mindit"]): dit = from_currsys(self.meta["mindit"]) ndit = int(np.floor(exptime / dit)) - print("Warning: The detector will be saturated!") + logger.warning("The detector will be saturated!") # ..todo: turn into proper warning - print("Exposure parameters:") - print(f" DIT: {dit:.3f} s NDIT: {ndit}") - print(f"Total exposure time: {dit * ndit:.3f} s") + logger.info("Exposure parameters: DIT=%.3f s NDIT=%d", dit, ndit) + logger.info("Total exposure time: %.3f s", dit * ndit) rc.__currsys__["!OBS.dit"] = dit rc.__currsys__["!OBS.ndit"] = ndit diff --git a/scopesim/effects/metis_lms_trace_list.py b/scopesim/effects/metis_lms_trace_list.py index 7a4422b9..d79c4867 100644 --- a/scopesim/effects/metis_lms_trace_list.py +++ b/scopesim/effects/metis_lms_trace_list.py @@ -1,6 +1,7 @@ """SpectralTraceList and SpectralTrace for the METIS LM spectrograph.""" -from copy import deepcopy +import warnings + import numpy as np from scipy.interpolate import RectBivariateSpline @@ -10,16 +11,20 @@ from astropy.wcs import WCS from astropy import units as u -from ..utils import from_currsys, find_file, quantify +from ..utils import from_currsys, find_file, quantify, get_logger from .spectral_trace_list import SpectralTraceList from .spectral_trace_list_utils import SpectralTrace from .spectral_trace_list_utils import Transform2D +from .spectral_trace_list_utils import make_image_interpolations from .apertures import ApertureMask from .ter_curves import TERCurve from ..base_classes import FieldOfViewBase, FOVSetupBase from ..optics.fov import FieldOfView +logger = get_logger(__name__) + + class MetisLMSSpectralTraceList(SpectralTraceList): """SpectralTraceList for the METIS LM spectrograph.""" @@ -29,22 +34,23 @@ class MetisLMSSpectralTraceList(SpectralTraceList): "slicewidth": 0.0207, # arcsec "pixscale": 0.0082, # arcsec "grat_spacing": 18.2, - "plate_scale": 0.303, + "fp2_platescale": 0.303, } def __init__(self, **kwargs): super().__init__(**kwargs) + # self.params = {"wavelen": "!OBS.wavelen"} # self.params.update(kwargs) self.wavelen = self.meta["wavelen"] # field of view of the instrument - # ..todo: get this from aperture list - self.slicelist = self._file["Aperture List"].data + # self.view = np.array([self.meta["naxis1"] * self.meta["pixscale"], # self.meta["nslice"] * self.meta["slicewidth"]]) + self.view = np.array([self.slicelist["right"].max() - self.slicelist["left"].min(), self.slicelist["top"].max() - @@ -67,6 +73,7 @@ def apply_to(self, obj, **kwargs): # the maximum wavelength range of LMS volumes = [spectral_trace.fov_grid() for spectral_trace in self.spectral_traces.values()] + wave_min = min(vol["wave_min"] for vol in volumes) wave_max = max(vol["wave_max"] for vol in volumes) extracted_vols = obj.extract(axes=["wave"], @@ -100,7 +107,7 @@ def apply_to(self, obj, **kwargs): ymin = spt.meta["fov"]["y_min"] ymax = spt.meta["fov"]["y_max"] - slicewcs = deepcopy(fovwcs) + slicewcs = fovwcs.deepcopy() slicewcs.wcs.ctype = ["LINEAR", "LINEAR", slicewcs.wcs.ctype[2]] @@ -161,6 +168,91 @@ def make_spectral_traces(self): self.spectral_traces = spec_traces + def rectify_cube(self, hdulist, xi_min=None, xi_max=None, interps=None, + **kwargs): + """ + Rectify an IFU observation into a data cube + + The HDU list (or fits file) must have been created with the + present OpticalTrain (or an identically configured one). + + Parameters + ---------- + hdulist : str or fits.HDUList + an ifu observation created with the present OpticalTrain + xi_min, xi_max : float [arcsec] + Spatial limits of the image slicer on the sky. For METIS LMS, + these values need not be provided by the user. + interps : list of interpolation functions + If provided, there must be one for each image extension in `hdulist`. + The functions go from pixels to the images and can be created with, + e.g., RectBivariateSpline. + """ + try: + inhdul = fits.open(hdulist) + except TypeError: + inhdul = hdulist + + # Create interpolation functions + if interps is None: + logger.info("Computing interpolation functions") + interps = make_image_interpolations(inhdul, kx=1, ky=1) + + # Create a common wcs for the rectification + dwave = from_currsys("!SIM.spectral.spectral_bin_width") + xi_min = np.min(self.slicelist["left"]) + xi_max = np.max(self.slicelist["right"]) + wave_min = self.meta["wave_min"] + wave_max = self.meta["wave_max"] + pixscale = self.meta["pixel_scale"] + naxis1 = int((xi_max - xi_min) / pixscale) + 1 + naxis2 = len(self.spectral_traces) + naxis3 = int((wave_max - wave_min)/dwave) + 1 + logger.debug("Cube: %d, %d, %d", naxis1, naxis2, naxis3) + logger.debug("Xi: %.2f, %.2f", xi_min, xi_max) + logger.debug("Wavelength: %.3f, %.3f", wave_min, wave_max) + slicewidth = (self.meta["y_max"] - self.meta["y_min"]) / naxis2 + + rectwcs = WCS(naxis=2) + rectwcs.wcs.ctype = ["WAVE", "LINEAR"] + rectwcs.wcs.crpix = [1, 1] + rectwcs.wcs.crval = [wave_min, xi_min] + rectwcs.wcs.cdelt = [dwave, pixscale] + rectwcs.wcs.cunit = ["um", "arcsec"] + + cube = np.zeros((naxis3, naxis2, naxis1), dtype=np.float32) + for i, spt in enumerate(self.spectral_traces.values()): + spt.wave_min = wave_min + spt.wave_max = wave_max + result = spt.rectify(hdulist, interps=interps, + wave_min=wave_min, wave_max=wave_max, + xi_min=xi_min, xi_max=xi_max, + bin_width=dwave) + cube[:, i, :] = result.data.T + + # FIXME: use wcs object here + cubehdr = fits.Header() + cubehdr["INSMODE"] = from_currsys(self.meta["element_name"]) + cubehdr["WAVELEN"] = from_currsys(self.meta["wavelen"]) + cubehdr["CTYPE1"] = "LINEAR" + cubehdr["CTYPE2"] = "LINEAR" + cubehdr["CTYPE3"] = "WAVE" + cubehdr["CRPIX1"] = (naxis1 + 1)/2 + cubehdr["CRPIX2"] = (naxis2 + 1)/2 + cubehdr["CRPIX3"] = 1. + cubehdr["CRVAL1"] = 0. + cubehdr["CRVAL2"] = 0. + cubehdr["CRVAL3"] = self.meta["wave_min"] + cubehdr["CDELT1"] = pixscale + cubehdr["CDELT2"] = slicewidth + cubehdr["CDELT3"] = dwave + cubehdr["CUNIT1"] = "arcsec" + cubehdr["CUNIT2"] = "arcsec" + cubehdr["CUNIT3"] = "um" + + cubehdu = fits.ImageHDU(data=cube, header=cubehdr) + return cubehdu + def _angle_from_lambda(self): """Determine optimal echelle rotation angle for wavelength.""" lam = from_currsys(self.meta["wavelen"]) @@ -178,7 +270,7 @@ class MetisLMSSpectralTrace(SpectralTrace): "slicewidth": 0.0207, # arcsec "pixscale": 0.0082, # arcsec "grat_spacing": 18.2, - "plate_scale": 0.303, + "fp2_platescale": 0.303, } def __init__(self, hdulist, spslice, params, **kwargs): @@ -205,6 +297,11 @@ def fov_grid(self): `x_max`, `y_max`. Spatial limits refer to the sky and are given in arcsec. """ + # TODO: Specify in the warning where the functionality should go! + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release. The functionality should be moved" + " somewhere else.", DeprecationWarning, stacklevel=2) + aperture = self._file["Aperture list"].data[self.meta["slice"]] x_min = aperture["left"] x_max = aperture["right"] @@ -239,7 +336,6 @@ def get_waverange(self, det_mm_lims): lam0 = from_currsys(self.meta["wavelen"]) xi0 = 0. ymid = self.xilam2y(xi0, lam0)[0] # estimate y level of trace - waverange = self.xy2lam(np.array([xmin, xmax]), np.array([ymid, ymid]), grid=False) @@ -306,10 +402,11 @@ def get_matrices(self): for i in range(4): for j in range(4): sel_ij = (subpoly["Row"] == i) * (subpoly["Col"] == j) - thematrix[i, j] = (subpoly["A11"][sel_ij] * angle**3 + - subpoly["A12"][sel_ij] * angle**2 + - subpoly["A21"][sel_ij] * angle + - subpoly["A22"][sel_ij]) + thematrix[i, j] = (subpoly["P3"][sel_ij] * angle**3 + + subpoly["P2"][sel_ij] * angle**2 + + subpoly["P1"][sel_ij] * angle + + subpoly["P0"][sel_ij]) + matrices[matnames[matid]] = thematrix return matrices @@ -350,11 +447,11 @@ def phase2lam(self, phase): def sky2fp(self, xi): """Convert position in arcsec to position in FP2.""" - return xi / self.meta["plate_scale"] + return xi / self.meta["fp2_platescale"] def fp2sky(self, fp_x): """Convert position in FP2 to position on sky.""" - return fp_x * self.meta["plate_scale"] + return fp_x * self.meta["fp2_platescale"] def __repr__(self): msg = (f"{self.__class__.__name__}({self._file!r}, " diff --git a/scopesim/effects/psfs.py b/scopesim/effects/psfs.py index d7457841..7e033b8b 100644 --- a/scopesim/effects/psfs.py +++ b/scopesim/effects/psfs.py @@ -1,3 +1,4 @@ +import warnings import numpy as np from scipy.signal import convolve from scipy.interpolate import RectBivariateSpline @@ -118,6 +119,8 @@ def apply_to(self, obj, **kwargs): def fov_grid(self, which="waveset", **kwargs): """See parent docstring.""" + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) waveset = [] if which == "waveset": if self._waveset is not None: @@ -210,6 +213,8 @@ def __init__(self, **kwargs): def fov_grid(self, which="waveset", **kwargs): """See parent docstring.""" + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) if which == "waveset": self.meta.update(kwargs) self.meta = utils.from_currsys(self.meta) @@ -285,18 +290,6 @@ def __init__(self, fwhm=1.5, **kwargs): self.meta["fwhm"] = fwhm self.meta["z_order"] = [242, 642] - # def fov_grid(self, which="waveset", **kwargs): - # wavelengths = [] - # if which == "waveset" and \ - # "waverange" in kwargs and \ - # "pixel_scale" in kwargs: - # waverange = utils.quantify(kwargs["waverange"], u.um) - # wavelengths = waverange - # # ..todo: return something useful - # - # # .. todo: check that this is actually correct - # return wavelengths - def get_kernel(self, fov): # called by .apply_to() from the base PSF class @@ -324,6 +317,8 @@ def __init__(self, diameter, **kwargs): def fov_grid(self, which="waveset", **kwargs): """See parent docstring.""" + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) wavelengths = [] if which == "waveset" and \ "waverange" in kwargs and \ diff --git a/scopesim/effects/shifts.py b/scopesim/effects/shifts.py index b99d3336..997f5240 100644 --- a/scopesim/effects/shifts.py +++ b/scopesim/effects/shifts.py @@ -1,3 +1,4 @@ +import warnings import numpy as np from astropy import units as u from astropy.table import Table @@ -24,6 +25,8 @@ def apply_to(self, obj, **kwargs): def fov_grid(self, which="shifts", **kwargs): """See parent docstring.""" + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) if which == "shifts": col_names = ["wavelength", "dx", "dy"] waves, dx, dy = [self.get_table(**kwargs)[col] @@ -222,6 +225,8 @@ def apply_to(self, fov, **kwargs): def fov_grid(self, which="shifts", **kwargs): """See parent docstring.""" + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) kwargs.update(self.meta) if "quick_adc" in self.meta: ad = AtmosphericDispersion(**self.meta) diff --git a/scopesim/effects/spectral_trace_list.py b/scopesim/effects/spectral_trace_list.py index 60b3e24f..36b8e6fe 100644 --- a/scopesim/effects/spectral_trace_list.py +++ b/scopesim/effects/spectral_trace_list.py @@ -88,15 +88,16 @@ class SpectralTraceList(Effect): """ - _class_params = {"x_colname": "x", - "y_colname": "y", - "s_colname": "s", - "wave_colname": "wavelength", - "col_number_start": 0, - "center_on_wave_mid": False, - "dwave": 0.002, # [um] for finding best fit dispersion - "invalid_value": None, # for dodgy trace file values - } + _class_params = { + "x_colname": "x", + "y_colname": "y", + "s_colname": "s", + "wave_colname": "wavelength", + "col_number_start": 0, + "center_on_wave_mid": False, + "dwave": 0.002, # [um] for finding best fit dispersion + "invalid_value": None, # for dodgy trace file values + } def __init__(self, **kwargs): super().__init__(**kwargs) @@ -104,23 +105,24 @@ def __init__(self, **kwargs): if "hdulist" in kwargs and isinstance(kwargs["hdulist"], fits.HDUList): self._file = kwargs["hdulist"] - params = {"z_order": [70, 270, 670], - "pixel_scale": "!INST.pixel_scale", # [arcsec / pix]} - "plate_scale": "!INST.plate_scale", # [arcsec / mm] - "spectral_bin_width": "!SIM.spectral.spectral_bin_width", # [um] - "wave_min": "!SIM.spectral.wave_min", # [um] - "wave_mid": "!SIM.spectral.wave_mid", # [um] - "wave_max": "!SIM.spectral.wave_max", # [um] - "x_colname": "x", - "y_colname": "y", - "s_colname": "s", - "wave_colname": "wavelength", - "center_on_wave_mid": False, - "dwave": 0.002, # [um] for finding the best fit dispersion - "invalid_value": None, # for dodgy trace file values - "report_plot_include": True, - "report_table_include": False, - } + params = { + "z_order": [70, 270, 670], + "pixel_scale": "!INST.pixel_scale", # [arcsec / pix]} + "plate_scale": "!INST.plate_scale", # [arcsec / mm] + "spectral_bin_width": "!SIM.spectral.spectral_bin_width", # [um] + "wave_min": "!SIM.spectral.wave_min", # [um] + "wave_mid": "!SIM.spectral.wave_mid", # [um] + "wave_max": "!SIM.spectral.wave_max", # [um] + "x_colname": "x", + "y_colname": "y", + "s_colname": "s", + "wave_colname": "wavelength", + "center_on_wave_mid": False, + "dwave": 0.002, # [um] for finding the best fit dispersion + "invalid_value": None, # for dodgy trace file values + "report_plot_include": True, + "report_table_include": False, + } self.meta.update(params) # Parameters that are specific to the subclass @@ -130,12 +132,13 @@ def __init__(self, **kwargs): if self._file is not None: self.make_spectral_traces() + self.update_meta() + def make_spectral_traces(self): """Return a dictionary of spectral traces read in from a file.""" self.ext_data = self._file[0].header["EDATA"] self.ext_cat = self._file[0].header["ECAT"] self.catalog = Table(self._file[self.ext_cat].data) - spec_traces = {} for row in self.catalog: params = {col: row[col] for col in row.colnames} @@ -145,6 +148,33 @@ def make_spectral_traces(self): self.spectral_traces = spec_traces + def update_meta(self): + """ + Update fov related meta values. + + The values describe the full extent of the spectral trace + volume in wavelength and space + """ + wlim, xlim, ylim = [], [], [] + for thetrace in self.spectral_traces.values(): + fov = thetrace.fov_grid() + if "wave_min" in fov: + wlim.extend([fov["wave_min"], fov["wave_max"]]) + if "x_min" in fov: + xlim.extend([fov["x_min"], fov["x_max"]]) + if "y_min" in fov: + ylim.extend([fov["y_min"], fov["y_max"]]) + + if wlim: + self.meta["wave_min"] = min(wlim) + self.meta["wave_max"] = max(wlim) + if xlim: + self.meta["x_min"] = min(xlim) + self.meta["x_max"] = max(xlim) + if ylim: + self.meta["y_min"] = min(ylim) + self.meta["y_max"] = max(ylim) + def apply_to(self, obj, **kwargs): """ Interface between ``FieldOfView`` and ``SpectralTraceList``. @@ -160,10 +190,14 @@ def apply_to(self, obj, **kwargs): """ if isinstance(obj, FOVSetupBase): # Setup of FieldOfView object - volumes = [spectral_trace.fov_grid() - for spectral_trace in self.spectral_traces.values()] + # volumes = [spectral_trace.fov_grid() + # for spectral_trace in self.spectral_traces.values()] + new_vols_list = [] - for vol in volumes: + + # for vol in volumes: + for spt in self.spectral_traces.values(): + vol = spt.fov_grid() wave_edges = [vol["wave_min"], vol["wave_max"]] if "x_min" in vol: x_edges = [vol["x_min"], vol["x_max"]] @@ -219,6 +253,7 @@ def footprint(self): @property def image_plane_header(self): + """Create and return header for the ImagePlane.""" x, y = self.footprint pixel_scale = from_currsys(self.meta["pixel_scale"]) hdr = header_from_list_of_xy(x, y, pixel_scale, "D") @@ -269,8 +304,8 @@ def rectify_traces(self, hdulist, xi_min=None, xi_max=None, interps=None, filtwaves = filtcurve.table["wavelength"] filtwave = filtwaves[filtcurve.table["transmission"] > 0.01] wave_min, wave_max = min(filtwave), max(filtwave) - logger.info("Full wavelength range: %.02f .. %.02f um", - wave_min, wave_max) + logger.info( + "Full wavelength range: %.02f .. %.02f um", wave_min, wave_max) if xi_min is None or xi_max is None: try: @@ -280,11 +315,11 @@ def rectify_traces(self, hdulist, xi_min=None, xi_max=None, interps=None, "Slit limits taken from header: %.02f .. %.02f arcsec", xi_min, xi_max) except KeyError: - logger.error(""" - Spatial slit limits (in arcsec) must be provided: - - either as method parameters xi_min and xi_max - - or as header keywords HIERARCH INS SLIT XIMIN/XIMAX - """) + logger.error( + "Spatial slit limits (in arcsec) must be provided:\n" + "- either as method parameters xi_min and xi_max\n" + "- or as header keywords HIERARCH INS SLIT XIMIN/XIMAX" + ) return None bin_width = kwargs.get("bin_width", None) @@ -429,19 +464,24 @@ class SpectralTraceListWheel(Effect): """ - required_keys = {"trace_list_names", "filename_format", - "current_trace_list"} + required_keys = { + "trace_list_names", + "filename_format", + "current_trace_list", + } _current_str = "current_trace_list" def __init__(self, **kwargs): super().__init__(**kwargs) check_keys(kwargs, self.required_keys, action="error") - params = {"z_order": [70, 270, 670], - "path": "", - "report_plot_include": True, - "report_table_include": True, - "report_table_rounding": 4} + params = { + "z_order": [70, 270, 670], + "path": "", + "report_plot_include": True, + "report_table_include": True, + "report_table_rounding": 4, + } self.meta.update(params) self.meta.update(kwargs) diff --git a/scopesim/effects/spectral_trace_list_utils.py b/scopesim/effects/spectral_trace_list_utils.py index e29390b9..0ca4aa11 100644 --- a/scopesim/effects/spectral_trace_list_utils.py +++ b/scopesim/effects/spectral_trace_list_utils.py @@ -9,6 +9,8 @@ - utility functions for use with spectral traces """ +import warnings + import numpy as np from scipy.interpolate import RectBivariateSpline @@ -42,17 +44,19 @@ class SpectralTrace: - x, y : [mm] """ - _class_params = {"x_colname": "x", - "y_colname": "y", - "s_colname": "s", - "wave_colname": "wavelength", - "dwave": 0.002, - "aperture_id": 0, - "image_plane_id": 0, - "extension_id": 2, - "spline_order": 4, - "pixel_size": None, - "description": ""} + _class_params = { + "x_colname": "x", + "y_colname": "y", + "s_colname": "s", + "wave_colname": "wavelength", + "dwave": 0.002, + "aperture_id": 0, + "image_plane_id": 0, + "extension_id": 2, + "spline_order": 4, + "pixel_size": None, + "description": "", + } def __init__(self, trace_tbl, **kwargs): # Within scopesim, the actual parameter values are @@ -95,6 +99,8 @@ def fov_grid(self): Spatial limits are determined by the `ApertureMask` effect and are not returned here. """ + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) aperture_id = self.meta["aperture_id"] lam_arr = self.table[self.meta["wave_colname"]] @@ -137,8 +143,8 @@ def compute_interpolation_functions(self): self.dispersion_axis = "x" else: self.dispersion_axis = "y" - logger.warning("Dispersion axis determined to be %s", - self.dispersion_axis) + logger.warning( + "Dispersion axis determined to be %s", self.dispersion_axis) def map_spectra_to_focal_plane(self, fov): """ @@ -183,8 +189,8 @@ def map_spectra_to_focal_plane(self, fov): # Check if spectral trace footprint is outside FoV if xmax < 0 or xmin > naxis1d or ymax < 0 or ymin > naxis2d: - logger.info("Spectral trace %s: footprint is outside FoV", - fov.trace_id) + logger.info( + "Spectral trace %d: footprint is outside FoV", fov.trace_id) return None # Only work on parts within the FoV @@ -215,7 +221,7 @@ def map_spectra_to_focal_plane(self, fov): xilam = XiLamImage(fov, self.dlam_per_pix) self._xilamimg = xilam # ..todo: remove or make available with a debug flag? except ValueError: - print(f" ---> {self.trace_id} gave ValueError") + logger.warning(" ---> %d gave ValueError", self.trace_id) npix_xi, npix_lam = xilam.npix_xi, xilam.npix_lam xilam_wcs = xilam.wcs @@ -276,7 +282,7 @@ def map_spectra_to_focal_plane(self, fov): if np.any(image < 0): logger.warning("map_spectra_to_focal_plane: %d negative pixels", - np.sum(image < 0)) + np.sum(image < 0)) image_hdu = fits.ImageHDU(header=img_header, data=image) return image_hdu @@ -309,7 +315,7 @@ def rectify(self, hdulist, interps=None, wcs=None, **kwargs): Spatial limits of the slit on the sky. This should be taken from the header of the hdulist, but this is not yet provided by scopesim """ - logger.info("Rectifying %s", self.trace_id) + logger.info("Rectifying %d", self.trace_id) wave_min = kwargs.get("wave_min", self.wave_min) @@ -323,6 +329,7 @@ def rectify(self, hdulist, interps=None, wcs=None, **kwargs): logger.info(" %.02f .. %.02f um", wave_min, wave_max) # bin_width is taken as the minimum dispersion of the trace + # ..todo: if wcs is given take bin width from cdelt1 bin_width = kwargs.get("bin_width", None) if bin_width is None: self._set_dispersion(wave_min, wave_max) @@ -421,7 +428,7 @@ def footprint(self, wave_min=None, wave_max=None, xi_min=None, xi_max=None): # This is only relevant if the trace is given by a table of reference # points. Otherwise (METIS LMS!) we assume that the range is valid. if ("wave_colname" in self.meta and - self.meta["wave_colname"] in self.table.colnames): + self.meta["wave_colname"] in self.table.colnames): # Here, the parameters are obtained from a table of reference points wave_unit = self.table[self.meta["wave_colname"]].unit wave_val = quantify(self.table[self.meta["wave_colname"]].data, @@ -973,8 +980,9 @@ def make_image_interpolations(hdulist, **kwargs): ) return interps - # ..todo: Check whether the following functions are actually used + + def rolling_median(x, n): """Calculate the rolling median of a sequence for +/- n entries.""" y = [np.median(x[max(0, i-n):min(len(x), i+n+1)]) for i in range(len(x))] @@ -1022,7 +1030,8 @@ def get_affine_parameters(coords): dxs = np.diff(coords["x"], axis=1) dys = np.diff(coords["y"], axis=1) - shears = np.array([np.arctan2(dys[i], dxs[i]) for i in range(dxs.shape[0])]) + shears = np.array([np.arctan2(dys[i], dxs[i]) + for i in range(dxs.shape[0])]) shears = np.array(list(shears.T) + [shears.T[-1]]).T shears = (np.average(shears, axis=0) * rad2deg) - (90 + rotations) diff --git a/scopesim/effects/surface_list.py b/scopesim/effects/surface_list.py index 367c16b6..c610b650 100644 --- a/scopesim/effects/surface_list.py +++ b/scopesim/effects/surface_list.py @@ -1,5 +1,6 @@ """TBA.""" +import warnings import numpy as np from astropy import units as u @@ -26,6 +27,8 @@ def __init__(self, **kwargs): self._emission = None def fov_grid(self, which="waveset", **kwargs): + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) wave_edges = [] if which == "waveset": self.meta.update(kwargs) diff --git a/scopesim/effects/ter_curves.py b/scopesim/effects/ter_curves.py index e97b480a..276e64aa 100644 --- a/scopesim/effects/ter_curves.py +++ b/scopesim/effects/ter_curves.py @@ -1,4 +1,5 @@ """Transmission, emissivity, reflection curves.""" +import warnings from collections.abc import Collection, Iterable import numpy as np @@ -204,10 +205,12 @@ def plot(self, which="x", wavelength=None, *, axes=None, **kwargs): wunit = params["wave_unit"] # TODO: shouldn't need both, make sure they're equal if wunit != wave_unit: - logger.warning(f"wavelength units in the meta dict of " - f"{self.meta.get('name')} are inconsistent: \n" - f"- wavelength_unit : {wave_unit} \n" - f"- wave_unit : {wunit}") + logger.warning("wavelength units in the meta dict of " + "%s are inconsistent:\n" + "- wavelength_unit : %s\n" + "- wave_unit : %s", + {self.meta.get("name")}, + wave_unit, wunit) wave = np.arange(quantify(params["wave_min"], wunit).value, quantify(params["wave_max"], wunit).value, @@ -220,7 +223,6 @@ def plot(self, which="x", wavelength=None, *, axes=None, **kwargs): abbrs = {"t": "transmission", "e": "emission", "r": "reflection", "x": "throughput"} - if not isinstance(axes, Iterable): axes = [axes] for ter, ax in zip(which, axes): @@ -419,6 +421,8 @@ def __init__(self, **kwargs): self.table["transmission"][mask] = 0 def fov_grid(self, which="waveset", **kwargs): + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) if which == "waveset": self.meta.update(kwargs) self.meta = from_currsys(self.meta) @@ -600,6 +604,8 @@ def throughput(self): return self.current_filter.throughput def fov_grid(self, which="waveset", **kwargs): + warnings.warn("The fov_grid method is deprecated and will be removed " + "in a future release.", DeprecationWarning, stacklevel=2) return self.current_filter.fov_grid(which=which, **kwargs) def change_filter(self, filtername=None): diff --git a/scopesim/optics/fov_manager.py b/scopesim/optics/fov_manager.py index 1196ba81..312a3935 100644 --- a/scopesim/optics/fov_manager.py +++ b/scopesim/optics/fov_manager.py @@ -166,6 +166,12 @@ def generate_fovs_list(self): @property def fovs(self): + # There two lines were not here before #258, but somehow not including + # them will mess things up as FOVs multipy like rabbits... + # Should investigate why at some point... + if self._fovs_list: + return self._fovs_list + if from_currsys(self.meta["preload_fovs"]) is False: self._fovs_list = self.generate_fovs_list() return self._fovs_list diff --git a/scopesim/optics/optical_train.py b/scopesim/optics/optical_train.py index e03692ca..65042803 100644 --- a/scopesim/optics/optical_train.py +++ b/scopesim/optics/optical_train.py @@ -1,6 +1,5 @@ import copy import sys -from pathlib import Path from datetime import datetime @@ -10,6 +9,7 @@ from tqdm import tqdm +from synphot import SourceSpectrum, Empirical1D from synphot.units import PHOTLAM from .optics_manager import OpticsManager @@ -227,6 +227,19 @@ def prepare_source(self, source): # Convert to PHOTLAM per arcsec2 # ..todo: this is not sufficiently general + for ispec, spec in enumerate(source.spectra): + # Put on fov wavegrid + wave_min = min(fov.meta["wave_min"] for fov in self.fov_manager.fovs) + wave_max = max(fov.meta["wave_max"] for fov in self.fov_manager.fovs) + wave_unit = u.Unit(from_currsys("!SIM.spectral.wave_unit")) + dwave = from_currsys("!SIM.spectral.spectral_bin_width") # Not a quantity + fov_waveset = np.arange(wave_min.value, wave_max.value, dwave) * wave_unit + fov_waveset = fov_waveset.to(u.um) + + source.spectra[ispec] = SourceSpectrum(Empirical1D, + points=fov_waveset, + lookup_table=spec(fov_waveset)) + for cube in source.cube_fields: header, data, wave = cube.header, cube.data, cube.wave @@ -236,7 +249,7 @@ def prepare_source(self, source): factor = 1 for base, power in zip(inunit.bases, inunit.powers): if (base**power).is_equivalent(u.arcsec**(-2)): - conversion =(base**power).to(u.arcsec**(-2)) / base**power + conversion = (base**power).to(u.arcsec**(-2)) / base**power data *= conversion factor = u.arcsec**(-2) diff --git a/scopesim/optics/optics_manager.py b/scopesim/optics/optics_manager.py index 9ecc20d8..acdc7130 100644 --- a/scopesim/optics/optics_manager.py +++ b/scopesim/optics/optics_manager.py @@ -247,7 +247,7 @@ def surfaces_table(self): else: # Avoid infinite recursion in Wheel effects (filter, adc) eff_copy = eff - sle_list_copy += [eff_copy] + sle_list_copy.append(eff_copy) comb_table = combine_surface_effects(sle_list_copy) return comb_table diff --git a/scopesim/server/database.py b/scopesim/server/database.py index 5ce0cd18..2db070df 100644 --- a/scopesim/server/database.py +++ b/scopesim/server/database.py @@ -4,10 +4,8 @@ from datetime import date from warnings import warn from pathlib import Path -from typing import Optional, Union, List, Tuple, Set, Dict -# Python 3.8 doesn't yet know these things....... -# from collections.abc import Iterator, Iterable, Mapping -from typing import Iterator, Iterable, Mapping +from typing import Optional, Union +from collections.abc import Iterator, Iterable, Mapping from more_itertools import first, last, groupby_transform @@ -23,7 +21,7 @@ logger = get_logger(__name__) _GrpVerType = Mapping[str, Iterable[str]] -_GrpItrType = Iterator[Tuple[str, List[str]]] +_GrpItrType = Iterator[tuple[str, list[str]]] class PkgNotFoundError(Exception): @@ -83,7 +81,7 @@ def _unparse_raw_version(raw_version: str, package_name: str) -> str: return f"{package_name}.{raw_version}.zip" -def _parse_package_version(package: str) -> Tuple[str, str]: +def _parse_package_version(package: str) -> tuple[str, str]: p_name, p_version = package.split(".", maxsplit=1) return p_name, _parse_raw_version(p_version) @@ -102,7 +100,7 @@ def get_latest(versions: Iterable[str]) -> str: return max(versions) -def get_all_stable(version_groups: _GrpVerType) -> Iterator[Tuple[str, str]]: +def get_all_stable(version_groups: _GrpVerType) -> Iterator[tuple[str, str]]: """ Yield the most recent version (stable or dev) of each package. @@ -113,7 +111,7 @@ def get_all_stable(version_groups: _GrpVerType) -> Iterator[Tuple[str, str]]: Yields ------ - Iterator[Tuple[str, str]] + Iterator[tuple[str, str]] Iterator of package name - latest stable version pairs. """ @@ -121,7 +119,7 @@ def get_all_stable(version_groups: _GrpVerType) -> Iterator[Tuple[str, str]]: yield (package_name, get_stable(versions)) -def get_all_latest(version_groups: _GrpVerType) -> Iterator[Tuple[str, str]]: +def get_all_latest(version_groups: _GrpVerType) -> Iterator[tuple[str, str]]: """ Yield the most recent stable (not "dev") version of each package. @@ -132,7 +130,7 @@ def get_all_latest(version_groups: _GrpVerType) -> Iterator[Tuple[str, str]]: Yields ------ - Iterator[Tuple[str, str]] + Iterator[tuple[str, str]] Iterator of package name - latest version pairs. """ @@ -140,7 +138,7 @@ def get_all_latest(version_groups: _GrpVerType) -> Iterator[Tuple[str, str]]: yield (package_name, get_latest(versions)) -def group_package_versions(all_packages: Iterable[Tuple[str, str]]) -> _GrpItrType: +def group_package_versions(all_packages: Iterable[tuple[str, str]]) -> _GrpItrType: """Group different versions of packages by package name.""" version_groups = groupby_transform(sorted(all_packages), keyfunc=first, @@ -149,7 +147,7 @@ def group_package_versions(all_packages: Iterable[Tuple[str, str]]) -> _GrpItrTy return version_groups -def crawl_server_dirs(client=None) -> Iterator[Tuple[str, Set[str]]]: +def crawl_server_dirs(client=None) -> Iterator[tuple[str, set[str]]]: """Search all folders on server for .zip files.""" if client is None: with create_client(get_base_url()) as client: @@ -157,17 +155,17 @@ def crawl_server_dirs(client=None) -> Iterator[Tuple[str, Set[str]]]: return for dir_name in get_server_folder_contents(client, "", "/"): - logger.info("Searching folder '%s'", dir_name) + logger.debug("Searching folder '%s'", dir_name) try: p_dir = get_server_folder_package_names(client, dir_name) except ValueError as err: - logger.info(err) + logger.debug(err) continue - logger.info("Found packages %s.", p_dir) + logger.debug("Found packages %s.", p_dir) yield dir_name, p_dir -def get_all_package_versions(client=None) -> Dict[str, List[str]]: +def get_all_package_versions(client=None) -> dict[str, list[str]]: """Gather all versions for all packages present in any folder on server.""" if client is None: with create_client(get_base_url()) as client: @@ -182,7 +180,7 @@ def get_all_package_versions(client=None) -> Dict[str, List[str]]: return grouped -def get_package_folders(client) -> Dict[str, str]: +def get_package_folders(client) -> dict[str, str]: """Map package names to server locations.""" folders_dict = {pkg: path.strip("/") for path, pkgs in dict(crawl_server_dirs(client)).items() @@ -190,7 +188,7 @@ def get_package_folders(client) -> Dict[str, str]: return folders_dict -def get_server_folder_package_names(client, dir_name: str) -> Set[str]: +def get_server_folder_package_names(client, dir_name: str) -> set[str]: """ Retrieve all unique package names present on server in `dir_name` folder. @@ -222,7 +220,7 @@ def get_server_folder_package_names(client, dir_name: str) -> Set[str]: return package_names -def get_all_packages_on_server() -> Iterator[Tuple[str, set]]: +def get_all_packages_on_server() -> Iterator[tuple[str, set]]: """ Retrieve all unique package names present on server in known folders. @@ -238,14 +236,14 @@ def get_all_packages_on_server() -> Iterator[Tuple[str, set]]: Yields ------ - Iterator[Tuple[str, set]] + Iterator[tuple[str, set]] Key-value pairs of folder and corresponding package names. """ yield from crawl_server_dirs() -def list_packages(pkg_name: Optional[str] = None) -> List[str]: +def list_packages(pkg_name: Optional[str] = None) -> list[str]: """ List all packages, or all variants of a single package. @@ -339,7 +337,7 @@ def _download_single_package(client, pkg_name: str, release: str, all_versions, def download_packages(pkg_names: Union[Iterable[str], str], release: str = "stable", - save_dir: Optional[str] = None) -> List[Path]: + save_dir: Optional[str] = None) -> list[Path]: """ Download one or more packages to the local disk. @@ -396,14 +394,14 @@ def download_packages(pkg_names: Union[Iterable[str], str], """ base_url = get_base_url() - print("Gathering information from server ...") - logger.info("Accessing %s", base_url) + logger.info("Gathering information from server ...") + logger.debug("Accessing %s", base_url) with create_client(base_url) as client: all_versions = get_all_package_versions(client) folders_dict = get_package_folders(client) - print("Connection successful, starting download ...") + logger.info("Connection successful, starting download ...") if isinstance(pkg_names, str): pkg_names = [pkg_names] @@ -416,9 +414,11 @@ def download_packages(pkg_names: Union[Iterable[str], str], client, pkg_name, release, all_versions, folders_dict, save_dir, padlen) except PkgNotFoundError as error: - logger.error("\n") # needed until tqdm redirect implemented + # Whole stack trace not useful for enduser. + # Could log it to file though... + # logger.exception(error) logger.error(error) - logger.error("Skipping download of package '%s'", pkg_name) + logger.warning("Skipping download of package '%s'", pkg_name) continue save_paths.append(pkg_path) diff --git a/scopesim/server/download_utils.py b/scopesim/server/download_utils.py index 7c0ad806..fe70d75b 100644 --- a/scopesim/server/download_utils.py +++ b/scopesim/server/download_utils.py @@ -3,9 +3,7 @@ import re -# Python 3.8 doesn't yet know these things....... -# from collections.abc import Iterator, Iterable, Mapping -from typing import Iterator +from collections.abc import Iterator from zipfile import ZipFile from pathlib import Path @@ -17,6 +15,7 @@ from tqdm import tqdm # from tqdm.contrib.logging import logging_redirect_tqdm # put with logging_redirect_tqdm(loggers=all_loggers): around tqdm +# Note: seems to work without that so far... from ..utils import get_logger diff --git a/scopesim/source/source_templates.py b/scopesim/source/source_templates.py index 3cda7cc0..13cb64e8 100644 --- a/scopesim/source/source_templates.py +++ b/scopesim/source/source_templates.py @@ -279,8 +279,6 @@ def vega_spectrum(mag=0): def st_spectrum(mag=0): - # ..todo: the waves vector is a bit random, in particular its length, but sets the resolution of - # the final spectrum in scopesim. Can this be make more general? waves = np.geomspace(100, 300000, 50000) sp = ConstFlux1D(amplitude=mag*u.STmag) @@ -288,8 +286,6 @@ def st_spectrum(mag=0): def ab_spectrum(mag=0): - # ..todo: the waves vector is a bit random, in particular its length, but sets the resolution of - # the final spectrum in scopesim. Can this be make more general? waves = np.geomspace(100, 300000, 50000) sp = ConstFlux1D(amplitude=mag * u.ABmag) diff --git a/scopesim/tests/conftest.py b/scopesim/tests/conftest.py index fab44def..137de192 100644 --- a/scopesim/tests/conftest.py +++ b/scopesim/tests/conftest.py @@ -18,16 +18,16 @@ @pytest.fixture(scope="package", autouse=True) def configure_logging(): - top_logger = logging.getLogger("astar") - handlers = top_logger.handlers + base_logger = logging.getLogger("astar") + handlers = base_logger.handlers # Disable handlers - top_logger.handlers = [] + base_logger.handlers = [] # Make sure logging can reach pytest's caplog - top_logger.propagate = True + base_logger.propagate = True yield # Restore - top_logger.handlers = handlers - top_logger.propagate = False + base_logger.handlers = handlers + base_logger.propagate = False @pytest.fixture(scope="package") diff --git a/scopesim/tests/test_basic_instrument/test_basic_instrument.py b/scopesim/tests/test_basic_instrument/test_basic_instrument.py index 0093f930..6f7934fb 100644 --- a/scopesim/tests/test_basic_instrument/test_basic_instrument.py +++ b/scopesim/tests/test_basic_instrument/test_basic_instrument.py @@ -95,15 +95,14 @@ def test_runs(self): plt.imshow(det_im) plt.show() - xs = [(175, 200), (500, 525), (825, 850)] + xs = [slice(172, 199), slice(495, 525), slice(821, 850)] dlams = np.array([0.35, 0.5, 0.75]) - n_spots = dlams / 0.05 + n_spots = (dlams / 0.05).round().astype(int) spot_flux = 28000 # due to psf flux losses in narrow slit (0.5") - for i in range(3): - x0, x1 = xs[i] - trace_flux = det_im[:, x0:x1].sum() # sum along a trace - assert round(trace_flux / spot_flux) == round(n_spots[i]) + for sl, n in zip(xs, n_spots): + trace_flux = det_im[:, sl].sum() # sum along a trace + assert round(trace_flux / spot_flux) == n @pytest.mark.usefixtures("protect_currsys", "patch_all_mock_paths") diff --git a/scopesim/tests/test_logging.py b/scopesim/tests/test_logging.py new file mode 100644 index 00000000..11da49dd --- /dev/null +++ b/scopesim/tests/test_logging.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +"""Test to make sure the logging configuration is applied.""" + +import logging +from importlib import reload + +import pytest + +import scopesim as sim + + +@pytest.fixture(scope="function") +def reload_scopesim(): + """Temprarily disable the global configure_logging fixture.""" + base_logger = logging.getLogger("astar") + handlers = base_logger.handlers + prop = base_logger.propagate + # Reload scopesim to apply logging configuration again + reload(sim) + yield + # Restore + base_logger.handlers = handlers + base_logger.propagate = prop + + +@pytest.mark.usefixtures("reload_scopesim") +def test_loggers_are_configured(): + log_dict = sim.rc.__config__["!SIM.logging"] + base_logger_dict = log_dict["loggers"]["astar"] + + base_logger = logging.getLogger("astar") + sim_logger = base_logger.getChild("scopesim") + + base_logger_level = logging.getLevelName(base_logger.getEffectiveLevel()) + assert base_logger_level == base_logger_dict["level"] + + sim_logger_level = logging.getLevelName(sim_logger.getEffectiveLevel()) + assert sim_logger_level == "DEBUG" + + assert base_logger.propagate == base_logger_dict["propagate"] + assert sim_logger.propagate + + for handler, name in zip(base_logger.handlers, + base_logger_dict["handlers"]): + handler.name == name + + +def test_log_to_file(): + base_logger = logging.getLogger("astar") + sim.log_to_file(enable=True) + assert any(handler.name == "file" for handler in base_logger.handlers) + sim.log_to_file(enable=False) + assert not any(handler.name == "file" for handler in base_logger.handlers) + + +def test_set_console_log_level(): + base_logger = logging.getLogger("astar") + sim.set_console_log_level("ERROR") + assert base_logger.handlers[0].level == logging.ERROR + sim.set_console_log_level() + assert base_logger.handlers[0].level == logging.INFO diff --git a/scopesim/tests/tests_effects/test_SpectralTraceList.py b/scopesim/tests/tests_effects/test_SpectralTraceList.py index 8a31e997..5041eb36 100644 --- a/scopesim/tests/tests_effects/test_SpectralTraceList.py +++ b/scopesim/tests/tests_effects/test_SpectralTraceList.py @@ -72,7 +72,6 @@ def fixture_spectral_trace_list(): """Instantiate a SpectralTraceList""" return SpectralTraceList(hdulist=tlo.make_trace_hdulist()) - class TestRectification: def test_rectify_cube_not_implemented(self, spectral_trace_list): hdulist = fits.HDUList() diff --git a/scopesim/tests/tests_effects/test_SpectralTraceListUtils.py b/scopesim/tests/tests_effects/test_SpectralTraceListUtils.py index f3e3d47f..21dadc9a 100644 --- a/scopesim/tests/tests_effects/test_SpectralTraceListUtils.py +++ b/scopesim/tests/tests_effects/test_SpectralTraceListUtils.py @@ -6,8 +6,12 @@ import pytest import numpy as np + +from astropy.io import fits + from scopesim.effects.spectral_trace_list_utils import SpectralTrace from scopesim.effects.spectral_trace_list_utils import Transform2D, power_vector +from scopesim.effects.spectral_trace_list_utils import make_image_interpolations from scopesim.tests.mocks.py_objects import trace_list_objects as tlo class TestSpectralTrace: @@ -128,3 +132,24 @@ def test_grid_false_shape_is_preserved(self, tf2d): n_x, n_y = 4, 2 res = tf2d(np.ones((n_y, n_x)), np.ones((n_y, n_x)), grid=False) assert res.shape == (n_y, n_x) + + +class TestImageInterpolations: + """Tests for function make_image_interpolations""" + def test_return_empty_for_table(self): + hdul = fits.HDUList([fits.PrimaryHDU(), fits.BinTableHDU()]) + assert make_image_interpolations(hdul) == [] + + def test_return_correct_number_of_interps(self): + nimg = 10 + hdul = fits.HDUList([fits.ImageHDU(data=np.ones((10, 10)))] * nimg) + interps = make_image_interpolations(hdul) + assert len(interps) == nimg + + def test_interpolation_is_accurate(self): + xx, yy = np.meshgrid(np.arange(100), np.arange(100)) + img = np.sin(xx/6) * np.cos(yy/7) + hdul = fits.HDUList(fits.ImageHDU(data=img)) + interps = make_image_interpolations(hdul, kx=1, ky=1) + imginterp = interps[0](yy, xx, grid=False) + assert np.allclose(imginterp, img) diff --git a/scopesim/utils.py b/scopesim/utils.py index b27ba880..043f0586 100644 --- a/scopesim/utils.py +++ b/scopesim/utils.py @@ -3,6 +3,7 @@ from pathlib import Path import sys import logging +from logging.config import dictConfig from collections import OrderedDict from collections.abc import Iterable, Generator from copy import deepcopy @@ -1034,3 +1035,35 @@ def wrapper(*args, **kwargs): raise return output return wrapper + + +def update_logging(capture_warnings=True): + """Reload logging configuration from ``rc.__config__``.""" + dictConfig(rc.__config__["!SIM.logging"]) + logging.captureWarnings(capture_warnings) + + # This cannot be in the dict config (yet) because NestedMapping doesn't like + # "." in keys (yet) ... + # Set the "astar.scopesim" logger + get_logger(__package__).setLevel(logging.DEBUG) + + +def log_to_file(enable=True): + """Enable or disable logging to file (convenience function).""" + if enable: + handlers = ["console", "file"] + else: + handlers = ["console"] + + rc.__config__["!SIM.logging.loggers.astar.handlers"] = handlers + update_logging() + + +def set_console_log_level(level="INFO"): + """Set the level for the console handler (convenience function). + + This controls what is actually printed to the console by ScopeSim. + Accepted values are: DEBUG, INFO (default), WARNING, ERROR and CRITICAL. + """ + rc.__config__["!SIM.logging.handlers.console.level"] = level + update_logging()