Merge branch 'master' into imjoy-support
This commit is contained in:
commit
dac1c9a748
21 changed files with 994 additions and 2122 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -1,3 +1,36 @@
|
|||
# [v2.10.0b0](https://gitlab.com/openflexure/openflexure-microscope-server/compare/v2.9.3...v2.10.0b0) (2021-05-18)
|
||||
|
||||
## New features
|
||||
* Improved auto gain, white balance, and lens shading table correction (!119). Fixes #191.
|
||||
The old auto calibration code has been replaced by a slower, more manual method that is more reliable.
|
||||
Individual parts of the calibration can be run independently (auto gain/exposure, auto white balance, lens shading).
|
||||
This should also improve the experience when using e.g. fluorescence imaging modes.
|
||||
|
||||
## Developer changes
|
||||
* Build instructions were updated (!115)
|
||||
* We've switched from `poetry` to `pipenv` to solve dependency management issues on the Raspberry Pi (!124). See the updated
|
||||
repository README for a full breakdown of what configuration information goes where. Closes #218.
|
||||
* Improved handling of stream settings and origin override (!125, !116, !117)
|
||||
This fixes a longstanding irritation when developing on localhost using `npm run serve`, where it was necessary to
|
||||
enter the API origin address every time the page was refreshed, and then manually re-enable the web stream. Both
|
||||
these issues are now fixed.
|
||||
* The API origin override field in the "dev tools" pane now remembers its value
|
||||
* The API origin can be overridden using the URL
|
||||
* Stream settings are now remembered in local storage
|
||||
|
||||
## Minor bug fixes and improvements
|
||||
* Better error handling for picamerax (!86)
|
||||
* Add coverage report to mypy (!110)
|
||||
* `MissingStage` can now be explicitly selected in microscope configuration (!112)
|
||||
* The first-run "tour" placement is improved and shouldn't block the interface any more (!114)
|
||||
* Fix placement of tour messages for icons ([afbb716](https://gitlab.com/openflexure/openflexure-microscope-server/commit/afbb716))
|
||||
* This closes #199 and helps with #193
|
||||
* The IHI interface now explicitly states scan style should be raster (!113)
|
||||
* Absolute moves are now fixed (!126), closing #220, #221, and #222.
|
||||
* Fixed a typing error with the camera stage mapping matrix and `numpy` 1.20 (!127).
|
||||
* Scans now have a minimum dimension of 1 in each axis, which avoids dividing by zero (!123)
|
||||
|
||||
|
||||
# [v2.9.3](https://gitlab.com/openflexure/openflexure-microscope-server/compare/v2.9.2...v2.9.3) (2020-12-14)
|
||||
|
||||
- Fix #194 ([0592d31](https://gitlab.com/openflexure/openflexure-microscope-server/commit/0592d31)), closes [#194](https://gitlab.com/openflexure/openflexure-microscope-server/issues/194)
|
||||
|
|
|
|||
645
Pipfile.lock
generated
645
Pipfile.lock
generated
|
|
@ -26,11 +26,11 @@
|
|||
"yaml"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:5ec0fe72f1422a1198973fcbb48d0eb5c7390f4b0fbe55474fce999ad6826a9b",
|
||||
"sha256:83e4d30edce987f4791f950c847d2b8cbe91e55af3d1ac7e3a35011a5c6fdbdd"
|
||||
"sha256:aaded4ae451752c58b70e579923d8db7daf0b71f62def8c8fe7a2a52bd7c06a2",
|
||||
"sha256:f49c49e0dee3fa545462d4536856458aae61b972727b3c32eabc7d6db8c074f8"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.3.0"
|
||||
"version": "==4.4.1"
|
||||
},
|
||||
"apispec-webframeworks": {
|
||||
"hashes": [
|
||||
|
|
@ -40,21 +40,13 @@
|
|||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.5.2"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
|
||||
"sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==1.4.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
||||
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
||||
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.3.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==21.2.0"
|
||||
},
|
||||
"camera-stage-mapping": {
|
||||
"hashes": [
|
||||
|
|
@ -73,14 +65,6 @@
|
|||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.4.4"
|
||||
},
|
||||
"colorzero": {
|
||||
"hashes": [
|
||||
"sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8",
|
||||
|
|
@ -155,11 +139,11 @@
|
|||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
|
||||
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
|
||||
"sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196",
|
||||
"sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==1.1.2"
|
||||
"version": "==1.1.4"
|
||||
},
|
||||
"flask-cors": {
|
||||
"hashes": [
|
||||
|
|
@ -185,11 +169,11 @@
|
|||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:c9db46394197244adf2f0b08ec5bc3cf16757e9590b02af1fca085c16c0d600a",
|
||||
"sha256:d2d46ef77ffc85cbf7dac7e81dd663fde71c45326131bea8033b9bad42268ebe"
|
||||
"sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581",
|
||||
"sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.10.0"
|
||||
"version": "==4.0.1"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
|
|
@ -225,103 +209,83 @@
|
|||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||
"sha256:19536834abffb3fa155017053c607cb835b2ecc6a3a2554a88043d991dffb736",
|
||||
"sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
|
||||
"sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
|
||||
"sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
|
||||
"sha256:3d61f15e39611aacd91b7e71d903787da86d9e80896e683c0103fced9add7834",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
|
||||
"sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:7952deddf24b85c88dab48f6ec366ac6e39d2761b5280f2f9594911e03fcd064",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
|
||||
"sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
|
||||
"sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||
"sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
|
||||
"sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
|
||||
"sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
|
||||
"sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
|
||||
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
|
||||
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
|
||||
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
|
||||
"sha256:060b790af48b3d1cb6776a1e8956fb41106c6befaca717181c41a1728147dc73",
|
||||
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
|
||||
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
|
||||
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
|
||||
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
|
||||
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
|
||||
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
|
||||
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
|
||||
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
|
||||
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
|
||||
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
|
||||
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
|
||||
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
|
||||
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
|
||||
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
|
||||
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
|
||||
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
|
||||
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
|
||||
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
|
||||
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
|
||||
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
|
||||
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
|
||||
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
|
||||
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
|
||||
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
|
||||
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
|
||||
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
|
||||
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
|
||||
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
|
||||
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
|
||||
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
||||
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"marshmallow": {
|
||||
"hashes": [
|
||||
"sha256:0dd42891a5ef288217ed6410917f3c6048f585f8692075a0052c24f9bfff9dfd",
|
||||
"sha256:16e99cb7f630c0ef4d7d364ed0109ac194268dde123966076ab3dafb9ae3906b"
|
||||
"sha256:8050475b70470cc58f4441ee92375db611792ba39ca1ad41d39cad193ea9e040",
|
||||
"sha256:b45cde981d1835145257b4a3c5cb7b80786dcf5f50dd2990749a50c16cb48e01"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==3.11.1"
|
||||
"version": "==3.12.1"
|
||||
},
|
||||
"numpy": {
|
||||
"hashes": [
|
||||
"sha256:2428b109306075d89d21135bdd6b785f132a1f5a3260c371cee1fae427e12727",
|
||||
"sha256:377751954da04d4a6950191b20539066b4e19e3b559d4695399c5e8e3e683bf6",
|
||||
"sha256:4703b9e937df83f5b6b7447ca5912b5f5f297aba45f91dbbbc63ff9278c7aa98",
|
||||
"sha256:471c0571d0895c68da309dacee4e95a0811d0a9f9f532a48dc1bea5f3b7ad2b7",
|
||||
"sha256:4b5a485d5aeed9b403d475dd3db750ae52f085f56f15c19a56b00ddf99597de5",
|
||||
"sha256:61d5b4cf73622e4d0c6b83408a16631b670fc045afd6540679aa35591a17fe6d",
|
||||
"sha256:6c915ee7dba1071554e70a3664a839fbc033e1d6528199d4621eeaaa5487ccd2",
|
||||
"sha256:6e51e417d9ae2e7848314994e6fc3832c9d426abce9328cf7571eefceb43e6c9",
|
||||
"sha256:719656636c48be22c23641859ff2419b27b6bdf844b36a2447cb39caceb00935",
|
||||
"sha256:780ae5284cb770ade51d4b4a7dce4faa554eb1d88a56d0e8b9f35fca9b0270ff",
|
||||
"sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee",
|
||||
"sha256:924dc3f83de20437de95a73516f36e09918e9c9c18d5eac520062c49191025fb",
|
||||
"sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042",
|
||||
"sha256:9c0fab855ae790ca74b27e55240fe4f2a36a364a3f1ebcfd1fb5ac4088f1cec3",
|
||||
"sha256:9cab23439eb1ebfed1aaec9cd42b7dc50fc96d5cd3147da348d9161f0501ada5",
|
||||
"sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6",
|
||||
"sha256:aa046527c04688af680217fffac61eec2350ef3f3d7320c07fd33f5c6e7b4d5f",
|
||||
"sha256:abc81829c4039e7e4c30f7897938fa5d4916a09c2c7eb9b244b7a35ddc9656f4",
|
||||
"sha256:bad70051de2c50b1a6259a6df1daaafe8c480ca98132da98976d8591c412e737",
|
||||
"sha256:c73a7975d77f15f7f68dacfb2bca3d3f479f158313642e8ea9058eea06637931",
|
||||
"sha256:d15007f857d6995db15195217afdbddfcd203dfaa0ba6878a2f580eaf810ecd6",
|
||||
"sha256:d76061ae5cab49b83a8cf3feacefc2053fac672728802ac137dd8c4123397677",
|
||||
"sha256:e8e4fbbb7e7634f263c5b0150a629342cc19b47c5eba8d1cd4363ab3455ab576",
|
||||
"sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935",
|
||||
"sha256:edb1f041a9146dcf02cd7df7187db46ab524b9af2515f392f337c7cbbf5b52cd"
|
||||
"sha256:1676b0a292dd3c99e49305a16d7a9f42a4ab60ec522eac0d3dd20cdf362ac010",
|
||||
"sha256:16f221035e8bd19b9dc9a57159e38d2dd060b48e93e1d843c49cb370b0f415fd",
|
||||
"sha256:43909c8bb289c382170e0282158a38cf306a8ad2ff6dfadc447e90f9961bef43",
|
||||
"sha256:4e465afc3b96dbc80cf4a5273e5e2b1e3451286361b4af70ce1adb2984d392f9",
|
||||
"sha256:55b745fca0a5ab738647d0e4db099bd0a23279c32b31a783ad2ccea729e632df",
|
||||
"sha256:5d050e1e4bc9ddb8656d7b4f414557720ddcca23a5b88dd7cff65e847864c400",
|
||||
"sha256:637d827248f447e63585ca3f4a7d2dfaa882e094df6cfa177cc9cf9cd6cdf6d2",
|
||||
"sha256:6690080810f77485667bfbff4f69d717c3be25e5b11bb2073e76bb3f578d99b4",
|
||||
"sha256:66fbc6fed94a13b9801fb70b96ff30605ab0a123e775a5e7a26938b717c5d71a",
|
||||
"sha256:67d44acb72c31a97a3d5d33d103ab06d8ac20770e1c5ad81bdb3f0c086a56cf6",
|
||||
"sha256:6ca2b85a5997dabc38301a22ee43c82adcb53ff660b89ee88dded6b33687e1d8",
|
||||
"sha256:6e51534e78d14b4a009a062641f465cfaba4fdcb046c3ac0b1f61dd97c861b1b",
|
||||
"sha256:70eb5808127284c4e5c9e836208e09d685a7978b6a216db85960b1a112eeace8",
|
||||
"sha256:830b044f4e64a76ba71448fce6e604c0fc47a0e54d8f6467be23749ac2cbd2fb",
|
||||
"sha256:8b7bb4b9280da3b2856cb1fc425932f46fba609819ee1c62256f61799e6a51d2",
|
||||
"sha256:a9c65473ebc342715cb2d7926ff1e202c26376c0dcaaee85a1fd4b8d8c1d3b2f",
|
||||
"sha256:c1c09247ccea742525bdb5f4b5ceeacb34f95731647fe55774aa36557dbb5fa4",
|
||||
"sha256:c5bf0e132acf7557fc9bb8ded8b53bbbbea8892f3c9a1738205878ca9434206a",
|
||||
"sha256:d840d80623c96696fab0d15489f49425bec1c0f47f3bf0b3362ce0c75dbad993",
|
||||
"sha256:db250fd3e90117e0312b611574cd1b3f78bec046783195075cbd7ba9c3d73f16",
|
||||
"sha256:e515c9a93aebe27166ec9593411c58494fa98e5fcc219e47260d9ab8a1cc7f9f",
|
||||
"sha256:e55185e51b18d788e49fe8305fd73ef4470596b33fc2c1ceb304566b99c71a69",
|
||||
"sha256:ea9cff01e75a956dbee133fa8e5b68f2f92175233de2f88de3a682dd94deda65",
|
||||
"sha256:f1452578d0516283c87608a5a5548b0cdde15b99650efdfd85182102ef7a7c17",
|
||||
"sha256:f39a995e47cb8649673cfa0579fbdd1cdd33ea497d1728a6cb194d6252268e48"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.20.2"
|
||||
"version": "==1.20.3"
|
||||
},
|
||||
"opencv-python-headless": {
|
||||
"hashes": [
|
||||
|
|
@ -470,11 +434,11 @@
|
|||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
|
||||
"sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
|
||||
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
|
||||
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==6.2.2"
|
||||
"version": "==6.2.4"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
|
|
@ -510,6 +474,7 @@
|
|||
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
|
||||
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
|
||||
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
|
||||
"sha256:8fc87fd1a1d30991d9198a4317e90dfaf3d2963c013608a019e8311568fa08b2",
|
||||
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
|
||||
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
|
||||
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
|
||||
|
|
@ -526,6 +491,16 @@
|
|||
],
|
||||
"version": "==5.4.1"
|
||||
},
|
||||
"rpi.gpio": {
|
||||
"hashes": [
|
||||
"sha256:5cd2f3c88d0b1e2f4780c905702c0474768c2bc113fb793121c2e117b401ab16",
|
||||
"sha256:6a4791f41cafc2ee6e4cb70e5bd31fadc66a0cfab29b38df8723a98f6f73ad5a",
|
||||
"sha256:7424bc6c205466764f30f666c18187a0824077daf20b295c42f08aea2cb87d3f",
|
||||
"sha256:7da5235aeba20da39ad4fb97f3ba3a2c6b2d60bba8958b00a4df42f608c6d42e"
|
||||
],
|
||||
"markers": "platform_machine == 'armv7l'",
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"sangaboard": {
|
||||
"hashes": [
|
||||
"sha256:3791159d57a749571f89acdce70aa9c1de82afef71f5186d5e2845c7be44698c",
|
||||
|
|
@ -535,37 +510,37 @@
|
|||
},
|
||||
"scipy": {
|
||||
"hashes": [
|
||||
"sha256:03f1fd3574d544456325dae502facdf5c9f81cbfe12808a5e67a737613b7ba8c",
|
||||
"sha256:0c81ea1a95b4c9e0a8424cf9484b7b8fa7ef57169d7bcc0dfcfc23e3d7c81a12",
|
||||
"sha256:1fba8a214c89b995e3721670e66f7053da82e7e5d0fe6b31d8e4b19922a9315e",
|
||||
"sha256:37f4c2fb904c0ba54163e03993ce3544c9c5cde104bcf90614f17d85bdfbb431",
|
||||
"sha256:50e5bcd9d45262725e652611bb104ac0919fd25ecb78c22f5282afabd0b2e189",
|
||||
"sha256:6ca1058cb5bd45388041a7c3c11c4b2bd58867ac9db71db912501df77be2c4a4",
|
||||
"sha256:77f7a057724545b7e097bfdca5c6006bed8580768cd6621bb1330aedf49afba5",
|
||||
"sha256:816951e73d253a41fa2fd5f956f8e8d9ac94148a9a2039e7db56994520582bf2",
|
||||
"sha256:96620240b393d155097618bcd6935d7578e85959e55e3105490bbbf2f594c7ad",
|
||||
"sha256:993c86513272bc84c451349b10ee4376652ab21f312b0554fdee831d593b6c02",
|
||||
"sha256:a9bc820c3df735fdd29f9a405cfc5347e1d53f0d420f8010b13502b6a33c0b57",
|
||||
"sha256:adf7cee8e5c92b05f2252af498f77c7214a2296d009fc5478fc432c2f8fb953b",
|
||||
"sha256:bc52d4d70863141bb7e2f8fd4d98e41d77375606cde50af65f1243ce2d7853e8",
|
||||
"sha256:c1d3f771c19af00e1a36f749bd0a0690cc64632783383bc68f77587358feb5a4",
|
||||
"sha256:d744657c27c128e357de2f0fd532c09c84cd6e4933e8232895a872e67059ac37",
|
||||
"sha256:e3e9742bad925c421d39e699daa8d396c57535582cba90017d17f926b61c1552",
|
||||
"sha256:e547f84cd52343ac2d56df0ab08d3e9cc202338e7d09fafe286d6c069ddacb31",
|
||||
"sha256:e89091e6a8e211269e23f049473b2fde0c0e5ae0dd5bd276c3fc91b97da83480",
|
||||
"sha256:e9da33e21c9bc1b92c20b5328adb13e5f193b924c9b969cd700c8908f315aa59",
|
||||
"sha256:ffdfb09315896c6e9ac739bb6e13a19255b698c24e6b28314426fd40a1180822"
|
||||
"sha256:01b38dec7e9f897d4db04f8de4e20f0f5be3feac98468188a0f47a991b796055",
|
||||
"sha256:10dbcc7de03b8d635a1031cb18fd3eaa997969b64fdf78f99f19ac163a825445",
|
||||
"sha256:19aeac1ad3e57338723f4657ac8520f41714804568f2e30bd547d684d72c392e",
|
||||
"sha256:1b21c6e0dc97b1762590b70dee0daddb291271be0580384d39f02c480b78290a",
|
||||
"sha256:1caade0ede6967cc675e235c41451f9fb89ae34319ddf4740194094ab736b88d",
|
||||
"sha256:208fe23655561a95ede6bb3bb15342882f6a8f3867bf5564cafbdbcc401b1a5d",
|
||||
"sha256:23995dfcf269ec3735e5a8c80cfceaf384369a47699df111a6246b83a55da582",
|
||||
"sha256:2a799714bf1f791fb2650d73222b248d18d53fd40d6af2df2c898db048189606",
|
||||
"sha256:3274ce145b5dc416c49c0cf8b6119f787f0965cd35e22058fe1932c09fe15d77",
|
||||
"sha256:33d1677d46111cfa1c84b87472a0274dde9ef4a7ef2e1f155f012f5f1e995d8f",
|
||||
"sha256:44d452850f77e65e25b1eb1ac01e25770323a782bfe3a1a3e43847ad4266d93d",
|
||||
"sha256:9e3302149a369697c6aaea18b430b216e3c88f9a61b62869f6104881e5f9ef85",
|
||||
"sha256:a75b014d3294fce26852a9d04ea27b5671d86736beb34acdfc05859246260707",
|
||||
"sha256:ad7269254de06743fb4768f658753de47d8b54e4672c5ebe8612a007a088bd48",
|
||||
"sha256:b30280fbc1fd8082ac822994a98632111810311a9ece71a0e48f739df3c555a2",
|
||||
"sha256:b79104878003487e2b4639a20b9092b02e1bad07fc4cf924b495cf413748a777",
|
||||
"sha256:d449d40e830366b4c612692ad19fbebb722b6b847f78a7b701b1e0d6cda3cc13",
|
||||
"sha256:d647757373985207af3343301d89fe738d5a294435a4f2aafb04c13b4388c896",
|
||||
"sha256:f68eb46b86b2c246af99fcaa6f6e37c7a7a413e1084a794990b877f2ff71f7b6",
|
||||
"sha256:fdf606341cd798530b05705c87779606fcdfaf768a8129c348ea94441da15b04"
|
||||
],
|
||||
"markers": "python_version < '3.10' and python_version >= '3.7'",
|
||||
"version": "==1.6.2"
|
||||
"version": "==1.6.3"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.15.0"
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
|
|
@ -629,11 +604,11 @@
|
|||
"yaml"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:5ec0fe72f1422a1198973fcbb48d0eb5c7390f4b0fbe55474fce999ad6826a9b",
|
||||
"sha256:83e4d30edce987f4791f950c847d2b8cbe91e55af3d1ac7e3a35011a5c6fdbdd"
|
||||
"sha256:aaded4ae451752c58b70e579923d8db7daf0b71f62def8c8fe7a2a52bd7c06a2",
|
||||
"sha256:f49c49e0dee3fa545462d4536856458aae61b972727b3c32eabc7d6db8c074f8"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.3.0"
|
||||
"version": "==4.4.1"
|
||||
},
|
||||
"apispec-webframeworks": {
|
||||
"hashes": [
|
||||
|
|
@ -652,35 +627,27 @@
|
|||
},
|
||||
"astroid": {
|
||||
"hashes": [
|
||||
"sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9",
|
||||
"sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df"
|
||||
"sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e",
|
||||
"sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.5.2"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
|
||||
"sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==1.4.0"
|
||||
"markers": "python_version ~= '3.6'",
|
||||
"version": "==2.5.6"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
|
||||
"sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
|
||||
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
|
||||
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.3.0"
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==21.2.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
|
||||
"sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
|
||||
"sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9",
|
||||
"sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.9.0"
|
||||
"version": "==2.9.1"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
|
|
@ -722,14 +689,6 @@
|
|||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==7.1.2"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
],
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==0.4.4"
|
||||
},
|
||||
"colorzero": {
|
||||
"hashes": [
|
||||
"sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8",
|
||||
|
|
@ -797,11 +756,11 @@
|
|||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
|
||||
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
|
||||
"sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
|
||||
"sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.16"
|
||||
"version": "==0.17.1"
|
||||
},
|
||||
"expiringdict": {
|
||||
"hashes": [
|
||||
|
|
@ -812,11 +771,11 @@
|
|||
},
|
||||
"flask": {
|
||||
"hashes": [
|
||||
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
|
||||
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
|
||||
"sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196",
|
||||
"sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==1.1.2"
|
||||
"version": "==1.1.4"
|
||||
},
|
||||
"flask-cors": {
|
||||
"hashes": [
|
||||
|
|
@ -867,11 +826,11 @@
|
|||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:c9db46394197244adf2f0b08ec5bc3cf16757e9590b02af1fca085c16c0d600a",
|
||||
"sha256:d2d46ef77ffc85cbf7dac7e81dd663fde71c45326131bea8033b9bad42268ebe"
|
||||
"sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581",
|
||||
"sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.10.0"
|
||||
"version": "==4.0.1"
|
||||
},
|
||||
"iniconfig": {
|
||||
"hashes": [
|
||||
|
|
@ -886,7 +845,7 @@
|
|||
"sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
|
||||
"sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
|
||||
],
|
||||
"markers": "python_version >= '3.6' and python_version < '4.0'",
|
||||
"markers": "python_version >= '3.6' and python_version < '4'",
|
||||
"version": "==5.8.0"
|
||||
},
|
||||
"itsdangerous": {
|
||||
|
|
@ -948,18 +907,24 @@
|
|||
"sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3",
|
||||
"sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2",
|
||||
"sha256:1822d16e5ac9061749955d099ff50c6cbc068f8cbfde28dfbeea845f7fbe6e09",
|
||||
"sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae",
|
||||
"sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f",
|
||||
"sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927",
|
||||
"sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3",
|
||||
"sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7",
|
||||
"sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59",
|
||||
"sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f",
|
||||
"sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade",
|
||||
"sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96",
|
||||
"sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468",
|
||||
"sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b",
|
||||
"sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4",
|
||||
"sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354",
|
||||
"sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83",
|
||||
"sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04",
|
||||
"sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16",
|
||||
"sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791",
|
||||
"sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a",
|
||||
"sha256:74f69060f98a70e3654de3021cd8162859e0f30b9e69aed76ef2b7a2e3b84212",
|
||||
"sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51",
|
||||
"sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1",
|
||||
|
|
@ -973,10 +938,14 @@
|
|||
"sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa",
|
||||
"sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106",
|
||||
"sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d",
|
||||
"sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617",
|
||||
"sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4",
|
||||
"sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92",
|
||||
"sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0",
|
||||
"sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4",
|
||||
"sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24",
|
||||
"sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2",
|
||||
"sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e",
|
||||
"sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0",
|
||||
"sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654",
|
||||
"sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2",
|
||||
|
|
@ -988,72 +957,52 @@
|
|||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||
"sha256:19536834abffb3fa155017053c607cb835b2ecc6a3a2554a88043d991dffb736",
|
||||
"sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
|
||||
"sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
|
||||
"sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
|
||||
"sha256:3d61f15e39611aacd91b7e71d903787da86d9e80896e683c0103fced9add7834",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
|
||||
"sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:7952deddf24b85c88dab48f6ec366ac6e39d2761b5280f2f9594911e03fcd064",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
|
||||
"sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
|
||||
"sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||
"sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
|
||||
"sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
|
||||
"sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
|
||||
"sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
|
||||
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
|
||||
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
|
||||
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
|
||||
"sha256:060b790af48b3d1cb6776a1e8956fb41106c6befaca717181c41a1728147dc73",
|
||||
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
|
||||
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
|
||||
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
|
||||
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
|
||||
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
|
||||
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
|
||||
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
|
||||
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
|
||||
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
|
||||
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
|
||||
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
|
||||
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
|
||||
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
|
||||
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
|
||||
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
|
||||
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
|
||||
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
|
||||
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
|
||||
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
|
||||
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
|
||||
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
|
||||
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
|
||||
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
|
||||
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
|
||||
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
|
||||
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
|
||||
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
|
||||
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
|
||||
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
|
||||
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
||||
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"marshmallow": {
|
||||
"hashes": [
|
||||
"sha256:0dd42891a5ef288217ed6410917f3c6048f585f8692075a0052c24f9bfff9dfd",
|
||||
"sha256:16e99cb7f630c0ef4d7d364ed0109ac194268dde123966076ab3dafb9ae3906b"
|
||||
"sha256:8050475b70470cc58f4441ee92375db611792ba39ca1ad41d39cad193ea9e040",
|
||||
"sha256:b45cde981d1835145257b4a3c5cb7b80786dcf5f50dd2990749a50c16cb48e01"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==3.11.1"
|
||||
"version": "==3.12.1"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
|
|
@ -1099,34 +1048,34 @@
|
|||
},
|
||||
"numpy": {
|
||||
"hashes": [
|
||||
"sha256:2428b109306075d89d21135bdd6b785f132a1f5a3260c371cee1fae427e12727",
|
||||
"sha256:377751954da04d4a6950191b20539066b4e19e3b559d4695399c5e8e3e683bf6",
|
||||
"sha256:4703b9e937df83f5b6b7447ca5912b5f5f297aba45f91dbbbc63ff9278c7aa98",
|
||||
"sha256:471c0571d0895c68da309dacee4e95a0811d0a9f9f532a48dc1bea5f3b7ad2b7",
|
||||
"sha256:4b5a485d5aeed9b403d475dd3db750ae52f085f56f15c19a56b00ddf99597de5",
|
||||
"sha256:61d5b4cf73622e4d0c6b83408a16631b670fc045afd6540679aa35591a17fe6d",
|
||||
"sha256:6c915ee7dba1071554e70a3664a839fbc033e1d6528199d4621eeaaa5487ccd2",
|
||||
"sha256:6e51e417d9ae2e7848314994e6fc3832c9d426abce9328cf7571eefceb43e6c9",
|
||||
"sha256:719656636c48be22c23641859ff2419b27b6bdf844b36a2447cb39caceb00935",
|
||||
"sha256:780ae5284cb770ade51d4b4a7dce4faa554eb1d88a56d0e8b9f35fca9b0270ff",
|
||||
"sha256:878922bf5ad7550aa044aa9301d417e2d3ae50f0f577de92051d739ac6096cee",
|
||||
"sha256:924dc3f83de20437de95a73516f36e09918e9c9c18d5eac520062c49191025fb",
|
||||
"sha256:97ce8b8ace7d3b9288d88177e66ee75480fb79b9cf745e91ecfe65d91a856042",
|
||||
"sha256:9c0fab855ae790ca74b27e55240fe4f2a36a364a3f1ebcfd1fb5ac4088f1cec3",
|
||||
"sha256:9cab23439eb1ebfed1aaec9cd42b7dc50fc96d5cd3147da348d9161f0501ada5",
|
||||
"sha256:a8e6859913ec8eeef3dbe9aed3bf475347642d1cdd6217c30f28dee8903528e6",
|
||||
"sha256:aa046527c04688af680217fffac61eec2350ef3f3d7320c07fd33f5c6e7b4d5f",
|
||||
"sha256:abc81829c4039e7e4c30f7897938fa5d4916a09c2c7eb9b244b7a35ddc9656f4",
|
||||
"sha256:bad70051de2c50b1a6259a6df1daaafe8c480ca98132da98976d8591c412e737",
|
||||
"sha256:c73a7975d77f15f7f68dacfb2bca3d3f479f158313642e8ea9058eea06637931",
|
||||
"sha256:d15007f857d6995db15195217afdbddfcd203dfaa0ba6878a2f580eaf810ecd6",
|
||||
"sha256:d76061ae5cab49b83a8cf3feacefc2053fac672728802ac137dd8c4123397677",
|
||||
"sha256:e8e4fbbb7e7634f263c5b0150a629342cc19b47c5eba8d1cd4363ab3455ab576",
|
||||
"sha256:e9459f40244bb02b2f14f6af0cd0732791d72232bbb0dc4bab57ef88e75f6935",
|
||||
"sha256:edb1f041a9146dcf02cd7df7187db46ab524b9af2515f392f337c7cbbf5b52cd"
|
||||
"sha256:1676b0a292dd3c99e49305a16d7a9f42a4ab60ec522eac0d3dd20cdf362ac010",
|
||||
"sha256:16f221035e8bd19b9dc9a57159e38d2dd060b48e93e1d843c49cb370b0f415fd",
|
||||
"sha256:43909c8bb289c382170e0282158a38cf306a8ad2ff6dfadc447e90f9961bef43",
|
||||
"sha256:4e465afc3b96dbc80cf4a5273e5e2b1e3451286361b4af70ce1adb2984d392f9",
|
||||
"sha256:55b745fca0a5ab738647d0e4db099bd0a23279c32b31a783ad2ccea729e632df",
|
||||
"sha256:5d050e1e4bc9ddb8656d7b4f414557720ddcca23a5b88dd7cff65e847864c400",
|
||||
"sha256:637d827248f447e63585ca3f4a7d2dfaa882e094df6cfa177cc9cf9cd6cdf6d2",
|
||||
"sha256:6690080810f77485667bfbff4f69d717c3be25e5b11bb2073e76bb3f578d99b4",
|
||||
"sha256:66fbc6fed94a13b9801fb70b96ff30605ab0a123e775a5e7a26938b717c5d71a",
|
||||
"sha256:67d44acb72c31a97a3d5d33d103ab06d8ac20770e1c5ad81bdb3f0c086a56cf6",
|
||||
"sha256:6ca2b85a5997dabc38301a22ee43c82adcb53ff660b89ee88dded6b33687e1d8",
|
||||
"sha256:6e51534e78d14b4a009a062641f465cfaba4fdcb046c3ac0b1f61dd97c861b1b",
|
||||
"sha256:70eb5808127284c4e5c9e836208e09d685a7978b6a216db85960b1a112eeace8",
|
||||
"sha256:830b044f4e64a76ba71448fce6e604c0fc47a0e54d8f6467be23749ac2cbd2fb",
|
||||
"sha256:8b7bb4b9280da3b2856cb1fc425932f46fba609819ee1c62256f61799e6a51d2",
|
||||
"sha256:a9c65473ebc342715cb2d7926ff1e202c26376c0dcaaee85a1fd4b8d8c1d3b2f",
|
||||
"sha256:c1c09247ccea742525bdb5f4b5ceeacb34f95731647fe55774aa36557dbb5fa4",
|
||||
"sha256:c5bf0e132acf7557fc9bb8ded8b53bbbbea8892f3c9a1738205878ca9434206a",
|
||||
"sha256:d840d80623c96696fab0d15489f49425bec1c0f47f3bf0b3362ce0c75dbad993",
|
||||
"sha256:db250fd3e90117e0312b611574cd1b3f78bec046783195075cbd7ba9c3d73f16",
|
||||
"sha256:e515c9a93aebe27166ec9593411c58494fa98e5fcc219e47260d9ab8a1cc7f9f",
|
||||
"sha256:e55185e51b18d788e49fe8305fd73ef4470596b33fc2c1ceb304566b99c71a69",
|
||||
"sha256:ea9cff01e75a956dbee133fa8e5b68f2f92175233de2f88de3a682dd94deda65",
|
||||
"sha256:f1452578d0516283c87608a5a5548b0cdde15b99650efdfd85182102ef7a7c17",
|
||||
"sha256:f39a995e47cb8649673cfa0579fbdd1cdd33ea497d1728a6cb194d6252268e48"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.20.2"
|
||||
"version": "==1.20.3"
|
||||
},
|
||||
"opencv-python-headless": {
|
||||
"hashes": [
|
||||
|
|
@ -1244,7 +1193,7 @@
|
|||
"sha256:6fb3021603d4421c6fcc40072bbcf150a6c52ef70ff4d3be089b8b04e015ef5a",
|
||||
"sha256:70b97cb194b978dc464c70793e85e6f746cddf82b84a38bfb135946ad71ae19c"
|
||||
],
|
||||
"markers": "python_version >= '3.6' and python_version < '4.0'",
|
||||
"markers": "python_version >= '3.6' and python_version < '4'",
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"psutil": {
|
||||
|
|
@ -1276,19 +1225,19 @@
|
|||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94",
|
||||
"sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"
|
||||
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
|
||||
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.8.1"
|
||||
"version": "==2.9.0"
|
||||
},
|
||||
"pylint": {
|
||||
"hashes": [
|
||||
"sha256:209d712ec870a0182df034ae19f347e725c1e615b2269519ab58a35b3fcbbe7a",
|
||||
"sha256:bd38914c7731cdc518634a8d3c5585951302b6e2b6de60fbb3f7a0220e21eeee"
|
||||
"sha256:586d8fa9b1891f4b725f587ef267abe2a1bad89d6b184520c7f07a253dd6e217",
|
||||
"sha256:f7e2072654a6b6afdf5e2fb38147d3e2d2d43c89f648637baab63e026481279b"
|
||||
],
|
||||
"markers": "python_version ~= '3.6'",
|
||||
"version": "==2.7.4"
|
||||
"version": "==2.8.2"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
|
|
@ -1307,11 +1256,11 @@
|
|||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
|
||||
"sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
|
||||
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
|
||||
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==6.2.2"
|
||||
"version": "==6.2.4"
|
||||
},
|
||||
"pytest-cov": {
|
||||
"hashes": [
|
||||
|
|
@ -1354,6 +1303,7 @@
|
|||
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
|
||||
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
|
||||
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
|
||||
"sha256:8fc87fd1a1d30991d9198a4317e90dfaf3d2963c013608a019e8311568fa08b2",
|
||||
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
|
||||
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
|
||||
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
|
||||
|
|
@ -1386,6 +1336,16 @@
|
|||
],
|
||||
"version": "==0.14.0"
|
||||
},
|
||||
"rpi.gpio": {
|
||||
"hashes": [
|
||||
"sha256:5cd2f3c88d0b1e2f4780c905702c0474768c2bc113fb793121c2e117b401ab16",
|
||||
"sha256:6a4791f41cafc2ee6e4cb70e5bd31fadc66a0cfab29b38df8723a98f6f73ad5a",
|
||||
"sha256:7424bc6c205466764f30f666c18187a0824077daf20b295c42f08aea2cb87d3f",
|
||||
"sha256:7da5235aeba20da39ad4fb97f3ba3a2c6b2d60bba8958b00a4df42f608c6d42e"
|
||||
],
|
||||
"markers": "platform_machine == 'armv7l'",
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"sangaboard": {
|
||||
"hashes": [
|
||||
"sha256:3791159d57a749571f89acdce70aa9c1de82afef71f5186d5e2845c7be44698c",
|
||||
|
|
@ -1395,37 +1355,37 @@
|
|||
},
|
||||
"scipy": {
|
||||
"hashes": [
|
||||
"sha256:03f1fd3574d544456325dae502facdf5c9f81cbfe12808a5e67a737613b7ba8c",
|
||||
"sha256:0c81ea1a95b4c9e0a8424cf9484b7b8fa7ef57169d7bcc0dfcfc23e3d7c81a12",
|
||||
"sha256:1fba8a214c89b995e3721670e66f7053da82e7e5d0fe6b31d8e4b19922a9315e",
|
||||
"sha256:37f4c2fb904c0ba54163e03993ce3544c9c5cde104bcf90614f17d85bdfbb431",
|
||||
"sha256:50e5bcd9d45262725e652611bb104ac0919fd25ecb78c22f5282afabd0b2e189",
|
||||
"sha256:6ca1058cb5bd45388041a7c3c11c4b2bd58867ac9db71db912501df77be2c4a4",
|
||||
"sha256:77f7a057724545b7e097bfdca5c6006bed8580768cd6621bb1330aedf49afba5",
|
||||
"sha256:816951e73d253a41fa2fd5f956f8e8d9ac94148a9a2039e7db56994520582bf2",
|
||||
"sha256:96620240b393d155097618bcd6935d7578e85959e55e3105490bbbf2f594c7ad",
|
||||
"sha256:993c86513272bc84c451349b10ee4376652ab21f312b0554fdee831d593b6c02",
|
||||
"sha256:a9bc820c3df735fdd29f9a405cfc5347e1d53f0d420f8010b13502b6a33c0b57",
|
||||
"sha256:adf7cee8e5c92b05f2252af498f77c7214a2296d009fc5478fc432c2f8fb953b",
|
||||
"sha256:bc52d4d70863141bb7e2f8fd4d98e41d77375606cde50af65f1243ce2d7853e8",
|
||||
"sha256:c1d3f771c19af00e1a36f749bd0a0690cc64632783383bc68f77587358feb5a4",
|
||||
"sha256:d744657c27c128e357de2f0fd532c09c84cd6e4933e8232895a872e67059ac37",
|
||||
"sha256:e3e9742bad925c421d39e699daa8d396c57535582cba90017d17f926b61c1552",
|
||||
"sha256:e547f84cd52343ac2d56df0ab08d3e9cc202338e7d09fafe286d6c069ddacb31",
|
||||
"sha256:e89091e6a8e211269e23f049473b2fde0c0e5ae0dd5bd276c3fc91b97da83480",
|
||||
"sha256:e9da33e21c9bc1b92c20b5328adb13e5f193b924c9b969cd700c8908f315aa59",
|
||||
"sha256:ffdfb09315896c6e9ac739bb6e13a19255b698c24e6b28314426fd40a1180822"
|
||||
"sha256:01b38dec7e9f897d4db04f8de4e20f0f5be3feac98468188a0f47a991b796055",
|
||||
"sha256:10dbcc7de03b8d635a1031cb18fd3eaa997969b64fdf78f99f19ac163a825445",
|
||||
"sha256:19aeac1ad3e57338723f4657ac8520f41714804568f2e30bd547d684d72c392e",
|
||||
"sha256:1b21c6e0dc97b1762590b70dee0daddb291271be0580384d39f02c480b78290a",
|
||||
"sha256:1caade0ede6967cc675e235c41451f9fb89ae34319ddf4740194094ab736b88d",
|
||||
"sha256:208fe23655561a95ede6bb3bb15342882f6a8f3867bf5564cafbdbcc401b1a5d",
|
||||
"sha256:23995dfcf269ec3735e5a8c80cfceaf384369a47699df111a6246b83a55da582",
|
||||
"sha256:2a799714bf1f791fb2650d73222b248d18d53fd40d6af2df2c898db048189606",
|
||||
"sha256:3274ce145b5dc416c49c0cf8b6119f787f0965cd35e22058fe1932c09fe15d77",
|
||||
"sha256:33d1677d46111cfa1c84b87472a0274dde9ef4a7ef2e1f155f012f5f1e995d8f",
|
||||
"sha256:44d452850f77e65e25b1eb1ac01e25770323a782bfe3a1a3e43847ad4266d93d",
|
||||
"sha256:9e3302149a369697c6aaea18b430b216e3c88f9a61b62869f6104881e5f9ef85",
|
||||
"sha256:a75b014d3294fce26852a9d04ea27b5671d86736beb34acdfc05859246260707",
|
||||
"sha256:ad7269254de06743fb4768f658753de47d8b54e4672c5ebe8612a007a088bd48",
|
||||
"sha256:b30280fbc1fd8082ac822994a98632111810311a9ece71a0e48f739df3c555a2",
|
||||
"sha256:b79104878003487e2b4639a20b9092b02e1bad07fc4cf924b495cf413748a777",
|
||||
"sha256:d449d40e830366b4c612692ad19fbebb722b6b847f78a7b701b1e0d6cda3cc13",
|
||||
"sha256:d647757373985207af3343301d89fe738d5a294435a4f2aafb04c13b4388c896",
|
||||
"sha256:f68eb46b86b2c246af99fcaa6f6e37c7a7a413e1084a794990b877f2ff71f7b6",
|
||||
"sha256:fdf606341cd798530b05705c87779606fcdfaf768a8129c348ea94441da15b04"
|
||||
],
|
||||
"markers": "python_version < '3.10' and python_version >= '3.7'",
|
||||
"version": "==1.6.2"
|
||||
"version": "==1.6.3"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
|
||||
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.15.0"
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
|
|
@ -1436,11 +1396,11 @@
|
|||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d",
|
||||
"sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"
|
||||
"sha256:b5c2ae4120bf00c799ba9b3699bc895816d272d120080fbc967292f29b52b48c",
|
||||
"sha256:d1cb10bee9c4231f1700ec2e24a91be3f3a3aba066ea4ca9f3bbe47e59d5a1d4"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==3.5.3"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.0.2"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
|
|
@ -1507,49 +1467,48 @@
|
|||
},
|
||||
"tomlkit": {
|
||||
"hashes": [
|
||||
"sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831",
|
||||
"sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618"
|
||||
"sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117",
|
||||
"sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.7.0"
|
||||
"version": "==0.7.2"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
|
||||
"sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
|
||||
"sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
|
||||
"sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
|
||||
"sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
|
||||
"sha256:4822449fb865605582a22d23997444ff3eaa3d0920b04b9902e37f8edbeadca6",
|
||||
"sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
|
||||
"sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
|
||||
"sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
|
||||
"sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
|
||||
"sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
|
||||
"sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
|
||||
"sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
|
||||
"sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
|
||||
"sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
|
||||
"sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
|
||||
"sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
|
||||
"sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
|
||||
"sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
|
||||
"sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
|
||||
"sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
|
||||
"sha256:a93908d202f52ec64e24a41031f1336a14b66706be2c9e51949647f7ac4cbd22",
|
||||
"sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
|
||||
"sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
|
||||
"sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
|
||||
"sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
|
||||
"sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
|
||||
"sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
|
||||
"sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
|
||||
"sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
|
||||
"sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
|
||||
"sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
|
||||
"sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace",
|
||||
"sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff",
|
||||
"sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266",
|
||||
"sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528",
|
||||
"sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6",
|
||||
"sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808",
|
||||
"sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4",
|
||||
"sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363",
|
||||
"sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341",
|
||||
"sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04",
|
||||
"sha256:6cd1c6665796c58380121c2dd3e6abef605a3ae1116b95cfc585370ebfefdd5c",
|
||||
"sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41",
|
||||
"sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e",
|
||||
"sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3",
|
||||
"sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899",
|
||||
"sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805",
|
||||
"sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c",
|
||||
"sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c",
|
||||
"sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39",
|
||||
"sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a",
|
||||
"sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3",
|
||||
"sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7",
|
||||
"sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f",
|
||||
"sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075",
|
||||
"sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0",
|
||||
"sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40",
|
||||
"sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428",
|
||||
"sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927",
|
||||
"sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3",
|
||||
"sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f",
|
||||
"sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"
|
||||
],
|
||||
"markers": "python_version < '3.8' and implementation_name == 'cpython'",
|
||||
"version": "==1.4.2"
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
|
|
@ -1565,7 +1524,7 @@
|
|||
"sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
|
||||
"sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.4"
|
||||
},
|
||||
"webargs": {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import os
|
|||
from datetime import datetime
|
||||
|
||||
import pkg_resources
|
||||
from flask import abort, send_file
|
||||
from flask import abort, send_file, jsonify
|
||||
from flask_cors import CORS, cross_origin
|
||||
from labthings import create_app
|
||||
from labthings.extensions import find_extensions
|
||||
|
|
@ -105,6 +105,20 @@ app, labthing = create_app(
|
|||
# Enable CORS for some routes outside of LabThings
|
||||
cors: CORS = CORS(app)
|
||||
|
||||
# Enable correct handling of Marshmallow/Webargs validation errors
|
||||
# Return validation errors as JSON
|
||||
# (see https://webargs.readthedocs.io/en/latest/framework_support.html)
|
||||
@app.errorhandler(422)
|
||||
@app.errorhandler(400)
|
||||
def handle_error(err):
|
||||
headers = err.data.get("headers", None)
|
||||
messages = err.data.get("messages", ["Invalid request."])
|
||||
if headers:
|
||||
return jsonify({"errors": messages}), err.code, headers
|
||||
else:
|
||||
return jsonify({"errors": messages}), err.code
|
||||
|
||||
|
||||
# Use custom JSON encoder
|
||||
labthing.json_encoder = JSONEncoder
|
||||
app.json_encoder = JSONEncoder
|
||||
|
|
|
|||
|
|
@ -83,6 +83,15 @@ class LoggingMoveWrapper:
|
|||
self._history: List[Tuple[float, Optional[CoordinateType]]] = []
|
||||
|
||||
|
||||
class CSMUncalibratedError(RuntimeError):
|
||||
"""A calibrated camera stage mapper is required, but this one is not calibrated.
|
||||
|
||||
The camera stage mapper requires calibration information to relate image pixels
|
||||
to stage coordinates. If a method attempts to retrieve this calibration before
|
||||
it exists, we raise this exception.
|
||||
"""
|
||||
|
||||
|
||||
class CSMExtension(BaseExtension):
|
||||
"""
|
||||
Use the camera as an encoder, so we can relate camera and stage coordinates
|
||||
|
|
@ -133,7 +142,12 @@ class CSMExtension(BaseExtension):
|
|||
def get_settings(self) -> Dict[str, Any]:
|
||||
"""Retrieve the settings for this extension"""
|
||||
keys: List[str] = ["extensions", self.name]
|
||||
return get_by_path(self.microscope.read_settings(), keys)
|
||||
try:
|
||||
return get_by_path(self.microscope.read_settings(), keys)
|
||||
except KeyError as exc:
|
||||
raise CSMUncalibratedError(
|
||||
"Camera stage mapping calibration data is missing"
|
||||
) from exc
|
||||
|
||||
def camera_stage_functions(self) -> Tuple[Callable, Callable, Callable, Callable]:
|
||||
"""Return functions that allow us to interface with the microscope"""
|
||||
|
|
@ -194,10 +208,13 @@ class CSMExtension(BaseExtension):
|
|||
@property
|
||||
def image_to_stage_displacement_matrix(self) -> np.ndarray: # 2x2 integer array
|
||||
"""A 2x2 matrix that converts displacement in image coordinates to stage coordinates."""
|
||||
displacement_matrix = self.get_settings().get("image_to_stage_displacement")
|
||||
if not displacement_matrix:
|
||||
raise ValueError("The microscope has not yet been calibrated.")
|
||||
return np.array(displacement_matrix)
|
||||
settings = self.get_settings()
|
||||
try:
|
||||
return np.array(settings["image_to_stage_displacement"])
|
||||
except KeyError as exc:
|
||||
raise CSMUncalibratedError(
|
||||
"The microscope has not yet been calibrated."
|
||||
) from exc
|
||||
|
||||
def move_in_image_coordinates(self, displacement_in_pixels: XYCoordinateType):
|
||||
"""Move by a given number of pixels on the camera"""
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
import logging
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterator, Tuple
|
||||
|
||||
import picamerax
|
||||
import labthings.fields as fields
|
||||
from flask import abort
|
||||
from labthings import find_component
|
||||
from labthings.extensions import BaseExtension
|
||||
from labthings.views import ActionView
|
||||
from picamerax import PiCamera
|
||||
|
||||
from openflexure_microscope.camera.base import BaseCamera
|
||||
from openflexure_microscope.microscope import Microscope
|
||||
|
||||
from .recalibrate_utils import (
|
||||
auto_expose_and_freeze_settings,
|
||||
adjust_shutter_and_gain_from_raw,
|
||||
adjust_white_balance_from_raw,
|
||||
flat_lens_shading_table,
|
||||
recalibrate_camera,
|
||||
get_channel_percentiles,
|
||||
lst_from_camera,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -61,64 +65,178 @@ class LSTExtension(BaseExtension):
|
|||
"/delete_lens_shading_table",
|
||||
endpoint="delete_lens_shading_table",
|
||||
)
|
||||
self.add_view(
|
||||
GetRawChannelPercentilesView,
|
||||
"/get_raw_channel_percentiles",
|
||||
endpoint="get_raw_channel_percentiles",
|
||||
)
|
||||
self.add_view(
|
||||
AutoExposureFromRawView,
|
||||
"/auto_exposure_from_raw",
|
||||
endpoint="auto_exposure_from_raw",
|
||||
)
|
||||
self.add_view(
|
||||
AutoWhiteBalanceFromRawView,
|
||||
"/auto_white_balance_from_raw",
|
||||
endpoint="auto_white_balance_from_raw",
|
||||
)
|
||||
self.add_view(
|
||||
AutoLensShadingTableView,
|
||||
"/auto_lens_shading_table",
|
||||
endpoint="auto_lens_shading_table",
|
||||
)
|
||||
|
||||
def recalibrate(self, microscope: Microscope):
|
||||
|
||||
@contextmanager
|
||||
def find_picamera() -> Iterator[Tuple[PiCamera, BaseCamera, Microscope]]:
|
||||
"""Locate the microscope and raise a sensible error if it's missing."""
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
|
||||
if not microscope:
|
||||
abort(
|
||||
503, "No microscope connected. Unable to use camera calibration functions."
|
||||
)
|
||||
|
||||
scamera = microscope.camera
|
||||
|
||||
if not hasattr(scamera, "picamera"):
|
||||
abort(503, "The PiCamera calibration plugin requires a Raspberry Pi camera.")
|
||||
|
||||
with scamera.lock:
|
||||
yield scamera.picamera, scamera, microscope
|
||||
|
||||
|
||||
class RecalibrateView(ActionView):
|
||||
def post(self):
|
||||
"""Reset the camera's settings.
|
||||
|
||||
This generates new gains, exposure time, and lens shading
|
||||
table such that the background is as uniform as possible
|
||||
with a gray level of 230. It takes a little while to run.
|
||||
|
||||
This consists of three steps:
|
||||
|
||||
* Reset gain and exposure time, then increase them until
|
||||
we have a suitably bright image
|
||||
* Set the auto white balance based on a raw image
|
||||
* Set the lens shading table based on a raw image
|
||||
|
||||
Each of these steps has its own endpoint if you want to
|
||||
perform them separately.
|
||||
"""
|
||||
with pause_stream(microscope.camera) as scamera:
|
||||
if hasattr(scamera, "picamera"):
|
||||
picamera_obj: picamerax.PiCamera = getattr(scamera, "picamera")
|
||||
auto_expose_and_freeze_settings(picamera_obj)
|
||||
recalibrate_camera(picamera_obj)
|
||||
microscope.save_settings()
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Recalibrate can only be used with a Raspberry Pi camera"
|
||||
)
|
||||
with find_picamera() as (picamera, scamera, microscope):
|
||||
logging.info("Starting microscope recalibration...")
|
||||
adjust_shutter_and_gain_from_raw(picamera)
|
||||
adjust_white_balance_from_raw(picamera)
|
||||
lst = lst_from_camera(picamera)
|
||||
with pause_stream(scamera):
|
||||
picamera.lens_shading_table = lst
|
||||
microscope.save_settings()
|
||||
|
||||
|
||||
class RecalibrateView(ActionView):
|
||||
class AutoLensShadingTableView(ActionView):
|
||||
def post(self):
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
"""Perform flat-field correction
|
||||
|
||||
if not microscope:
|
||||
abort(503, "No microscope connected. Unable to recalibrate.")
|
||||
|
||||
logging.info("Starting microscope recalibration...")
|
||||
|
||||
return self.extension.recalibrate(microscope)
|
||||
This routine acquires a new image (which should be an
|
||||
empty field of view, i.e. a perfect microscope would
|
||||
record a uniform white image), and then uses it to set
|
||||
the "lens shading table" such that future images will
|
||||
be corrected for vignetting.
|
||||
"""
|
||||
with find_picamera() as (picamera, scamera, microscope):
|
||||
logging.info("Generating lens shading table")
|
||||
lst = lst_from_camera(picamera)
|
||||
with pause_stream(scamera):
|
||||
picamera.lens_shading_table = lst
|
||||
microscope.save_settings()
|
||||
|
||||
|
||||
class FlattenLSTView(ActionView):
|
||||
def post(self):
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
|
||||
if not microscope:
|
||||
abort(
|
||||
503,
|
||||
"No microscope connected. Unable to flatten the lens shading table.",
|
||||
)
|
||||
|
||||
with pause_stream(microscope.camera) as scamera:
|
||||
flat_lst = flat_lens_shading_table(scamera.camera)
|
||||
scamera.camera.lens_shading_table = flat_lst
|
||||
microscope.save_settings()
|
||||
with find_picamera() as (picamera, scamera, microscope):
|
||||
with pause_stream(scamera):
|
||||
flat_lst = flat_lens_shading_table(picamera)
|
||||
picamera.lens_shading_table = flat_lst
|
||||
microscope.save_settings()
|
||||
|
||||
|
||||
class DeleteLSTView(ActionView):
|
||||
def post(self):
|
||||
microscope = find_component("org.openflexure.microscope")
|
||||
with find_picamera() as (picamera, scamera, microscope):
|
||||
with pause_stream(scamera):
|
||||
picamera.lens_shading_table = None
|
||||
microscope.save_settings()
|
||||
|
||||
if not microscope:
|
||||
abort(
|
||||
503,
|
||||
"No microscope connected. Unable to flatten the lens shading table.",
|
||||
)
|
||||
|
||||
with pause_stream(microscope.camera) as scamera:
|
||||
scamera.camera.lens_shading_table = None
|
||||
microscope.save_settings()
|
||||
class AutoExposureFromRawView(ActionView):
|
||||
args = {
|
||||
"target_white_level": fields.Int(
|
||||
missing=700,
|
||||
example=700,
|
||||
description=(
|
||||
"The pixel value (10-bit format) that we aim for when adjusting shutter/gain."
|
||||
),
|
||||
),
|
||||
"max_iterations": fields.Int(
|
||||
missing=20,
|
||||
description=(
|
||||
"The number of adjustments to the camera's settings to make before giving up."
|
||||
),
|
||||
),
|
||||
"tolerance": fields.Float(
|
||||
missing=0.05,
|
||||
example=0.05,
|
||||
description=(
|
||||
"We stop adjusting when we get within this fraction of the target "
|
||||
"value. It is a number between 0 and 1, usually 0.01--0.1."
|
||||
),
|
||||
),
|
||||
"percentile": fields.Float(
|
||||
missing=99.9,
|
||||
example=99.9,
|
||||
description=(
|
||||
"A float between 0 and 100 setting the centile to use "
|
||||
"to measure the white point of the image. A value "
|
||||
"of 99.9 allows 0.1% of the pixels to be erroneously "
|
||||
"bright - this helps stability in low light."
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
def post(self, args):
|
||||
with find_picamera() as (picamera, _, _):
|
||||
adjust_shutter_and_gain_from_raw(picamera, **args)
|
||||
|
||||
|
||||
class AutoWhiteBalanceFromRawView(ActionView):
|
||||
args = {
|
||||
"percentile": fields.Float(
|
||||
missing=99.9,
|
||||
example=99.9,
|
||||
description=(
|
||||
"A float between 0 and 100 setting the centile to use "
|
||||
"to measure the white point of the image. A value "
|
||||
"of 99.9 allows 0.1% of the pixels to be erroneously "
|
||||
"bright - this helps stability in low light."
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
def post(self, args):
|
||||
with find_picamera() as (picamera, _, _):
|
||||
adjust_white_balance_from_raw(picamera, **args)
|
||||
|
||||
|
||||
class GetRawChannelPercentilesView(ActionView):
|
||||
args = {
|
||||
"percentile": fields.Float(
|
||||
example=99.9,
|
||||
description="A float between 0 and 100 setting the centile to calculate",
|
||||
)
|
||||
}
|
||||
schema = fields.List(fields.Integer)
|
||||
|
||||
def post(self, args):
|
||||
with find_picamera() as (picamera, _, _):
|
||||
return get_channel_percentiles(picamera, args["percentile"])
|
||||
|
|
|
|||
|
|
@ -1,7 +1,39 @@
|
|||
"""
|
||||
Functions to set up a Raspberry Pi Camera v2 for scientific use
|
||||
|
||||
This module provides slower, simpler functions to set the
|
||||
gain, exposure, and white balance of a Raspberry Pi camera, using
|
||||
`picamerax` (a fork of `picamera`) to get as-manual-as-possible
|
||||
control over the camera. It's mostly used by the OpenFlexure
|
||||
Microscope, though it deliberately has no hard dependencies on
|
||||
said software, so that it's useful on its own.
|
||||
|
||||
There are three main calibration steps:
|
||||
|
||||
* Setting exposure time and gain to get a reasonably bright
|
||||
image.
|
||||
* Fixing the white balance to get a neutral image
|
||||
* Taking a uniform white image and using it to calibrate
|
||||
the Lens Shading Table
|
||||
|
||||
The most reliable way to do this, avoiding any issues relating
|
||||
to "memory" or nonlinearities in the camera's image processing
|
||||
pipeline, is to use raw images. This is quite slow, but very
|
||||
reliable. The three steps above can be accomplished by:
|
||||
|
||||
```
|
||||
picamera = picamerax.PiCamera()
|
||||
|
||||
adjust_shutter_and_gain_from_raw(picamera)
|
||||
adjust_white_balance_from_raw(picamera)
|
||||
lst = lst_from_camera(picamera)
|
||||
picamera.lens_shading_table = lst
|
||||
```
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from fractions import Fraction
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import List, NamedTuple, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from picamerax import PiCamera
|
||||
|
|
@ -33,48 +65,190 @@ def flat_lens_shading_table(camera: PiCamera) -> np.ndarray:
|
|||
|
||||
|
||||
def adjust_exposure_to_setpoint(camera: PiCamera, setpoint: int):
|
||||
"""Adjust the camera's exposure time until the maximum pixel value is <setpoint>."""
|
||||
print("Adjusting shutter speed to hit setpoint {}".format(setpoint), end="")
|
||||
"""Adjust the camera's exposure time until the maximum pixel value is <setpoint>.
|
||||
|
||||
NB this method uses RGB images (i.e. processed ones) not raw images.
|
||||
"""
|
||||
logging.info(f"Adjusting shutter speed to hit setpoint {setpoint}")
|
||||
for _ in range(3):
|
||||
print(".", end="")
|
||||
camera.shutter_speed = int(
|
||||
camera.shutter_speed * setpoint / np.max(rgb_image(camera))
|
||||
)
|
||||
time.sleep(1)
|
||||
print("done")
|
||||
|
||||
|
||||
def auto_expose_and_freeze_settings(camera: PiCamera):
|
||||
"""Freeze the settings after auto-exposing to white illumination"""
|
||||
logging.info("Allowing the camera to auto-expose")
|
||||
if "greyworld" in camera.AWB_MODES:
|
||||
print("Calibrating with greyworld AWB")
|
||||
camera.awb_mode = "greyworld"
|
||||
else:
|
||||
print("Calibrating with auto AWB")
|
||||
camera.awb_mode = "auto"
|
||||
camera.exposure_mode = "auto"
|
||||
camera.iso = (
|
||||
0 # This is important, if it's on a fixed ISO, gain might not set properly.
|
||||
)
|
||||
for _ in range(6):
|
||||
print(".", end="")
|
||||
time.sleep(0.5)
|
||||
logging.info("done")
|
||||
|
||||
logging.info("Freezing the camera settings...")
|
||||
camera.shutter_speed = camera.exposure_speed
|
||||
logging.info("Shutter speed = %s", (camera.shutter_speed))
|
||||
def set_minimum_exposure(camera: PiCamera):
|
||||
"""Enable manual exposure, with low gain and shutter speed
|
||||
|
||||
We set exposure mode to manual, analog and digital gain
|
||||
to 1, and shutter speed to the minimum (8us for Pi Camera v2)
|
||||
NB ISO is left at auto, because this is needed for the gains
|
||||
to be set correctly.
|
||||
"""
|
||||
camera.exposure_mode = "off"
|
||||
logging.info("Auto exposure disabled")
|
||||
g: Tuple[Fraction, Fraction] = camera.awb_gains
|
||||
camera.awb_mode = "off"
|
||||
camera.awb_gains = g
|
||||
logging.info("Auto white balance disabled, gains are %s", (g))
|
||||
camera.iso = 0 # We must set ISO=0 (auto) or we can't set gain
|
||||
camera.analog_gain = 1
|
||||
camera.digital_gain = 1
|
||||
# Setting the shutter speed to 1us will result in it being set
|
||||
# to the minimum possible, which is probably 8us for PiCamera v2
|
||||
camera.shutter_speed = 1
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
class ExposureTest(NamedTuple):
|
||||
"""Record the results of testing the camera's current exposure settings"""
|
||||
|
||||
level: int
|
||||
shutter_speed: int
|
||||
analog_gain: float
|
||||
|
||||
|
||||
def test_exposure_settings(camera: PiCamera, percentile: float) -> ExposureTest:
|
||||
"""Evaluate current exposure settings using a raw image
|
||||
|
||||
We will acquire a raw image and calculate the given percentile
|
||||
of the pixel values. We return a dictionary containing the
|
||||
percentile (which will be compared to the target), as well as
|
||||
the camera's shutter and gain values.
|
||||
"""
|
||||
max_brightness = np.max(get_channel_percentiles(camera, percentile))
|
||||
# The reported brightness can, theoretically, be negative or zero
|
||||
# because of black level compensation. The line below forces a
|
||||
# minimum value of 1 which will keep things well-behaved!
|
||||
if max_brightness < 1:
|
||||
logging.warning(
|
||||
f"Measured brightness of {max_brightness}. "
|
||||
"This should normally be >= 1, and may indicate the "
|
||||
"camera's black level compensation has gone wrong."
|
||||
)
|
||||
max_brightness = 1
|
||||
shutter_speed = int(camera.shutter_speed)
|
||||
analog_gain = float(camera.analog_gain)
|
||||
logging.info(
|
||||
"Analogue gain: %s, Digital gain: %s", camera.analog_gain, camera.digital_gain
|
||||
f"Brightness: {max_brightness: >5.0f}, "
|
||||
f"Gain: {analog_gain: >4.1f}, "
|
||||
f"Shutter: {shutter_speed: >7.0f}"
|
||||
)
|
||||
adjust_exposure_to_setpoint(camera, 215)
|
||||
return ExposureTest(max_brightness, shutter_speed, analog_gain)
|
||||
|
||||
|
||||
def check_convergence(test: ExposureTest, target: int, tolerance: float):
|
||||
"""Check whether the brightness is within the specified target range"""
|
||||
converged = abs(test.level - target) < target * tolerance
|
||||
return converged
|
||||
|
||||
|
||||
def adjust_shutter_and_gain_from_raw(
|
||||
camera: PiCamera,
|
||||
target_white_level: int = 700,
|
||||
max_iterations: int = 20,
|
||||
tolerance: float = 0.05,
|
||||
percentile: float = 99.9,
|
||||
) -> float:
|
||||
"""Adjust exposure and analog gain based on raw images.
|
||||
|
||||
This routine is slow but effective. It uses raw images, so we
|
||||
are not affected by white balance or digital gain.
|
||||
|
||||
|
||||
Arguments:
|
||||
target_white_level:
|
||||
The raw, 10-bit value we aim for. The brightest pixels
|
||||
should be approximately this bright. Maximum possible
|
||||
is about 900, 700 is reasonable.
|
||||
max_iterations:
|
||||
We will terminate once we perform this many iterations,
|
||||
whether or not we converge. More than 10 shouldn't happen.
|
||||
tolerance:
|
||||
How close to the target value we consider "done". Expressed
|
||||
as a fraction of the ``target_white_level`` so 0.05 means
|
||||
+/- 5%
|
||||
percentile:
|
||||
Rather then use the maximum value for each channel, we
|
||||
calculate a percentile. This makes us robust to single
|
||||
pixels that are bright/noisy. 99.9% still picks the top
|
||||
of the brightness range, but seems much more reliable
|
||||
than just ``np.max()``.
|
||||
"""
|
||||
if target_white_level * (tolerance + 1) >= 959:
|
||||
raise ValueError(
|
||||
"The target level is too high - a saturated image would be "
|
||||
"considered successful. target_white_level * (tolerance + 1) "
|
||||
"must be less than 959."
|
||||
)
|
||||
|
||||
set_minimum_exposure(camera)
|
||||
|
||||
# We start with very low exposure settings and work up
|
||||
# until either the brightness is high enough, or we can't increase the
|
||||
# shutter speed any more.
|
||||
iterations = 0
|
||||
while iterations < max_iterations:
|
||||
test = test_exposure_settings(camera, percentile)
|
||||
|
||||
if check_convergence(test, target_white_level, tolerance):
|
||||
break
|
||||
iterations += 1
|
||||
|
||||
# Adjust shutter speed so that the brightness approximates the target
|
||||
# NB we put a maximum of 32 on this, to stop it increasing too quickly.
|
||||
camera.shutter_speed = int(
|
||||
test.shutter_speed * min(target_white_level / test.level, 32)
|
||||
)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check whether the shutter speed is still going up - if not, we've hit a maximum
|
||||
if camera.shutter_speed == test.shutter_speed:
|
||||
logging.info("Shutter speed has maxed out.")
|
||||
break
|
||||
|
||||
# Now, if we've not converged, increase gain until we converge or run out of options.
|
||||
while iterations < max_iterations:
|
||||
test = test_exposure_settings(camera, percentile)
|
||||
if check_convergence(test, target_white_level, tolerance):
|
||||
break
|
||||
iterations += 1
|
||||
|
||||
# Adjust gain to make the white level hit the target, again with a maximum
|
||||
camera.analog_gain *= min(target_white_level / test.level, 2)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check the gain is still changing - if not, we have probably hit the maximum
|
||||
if camera.analog_gain == test.analog_gain:
|
||||
logging.info("Gain has maxed out.")
|
||||
break
|
||||
|
||||
if check_convergence(test, target_white_level, tolerance):
|
||||
logging.info(f"Brightness has converged to within {tolerance * 100 :.0f}%.")
|
||||
else:
|
||||
logging.warning(
|
||||
f"Failed to reach target brightness of {target_white_level}."
|
||||
f"Brightness reached {test.level} after {iterations} iterations."
|
||||
)
|
||||
|
||||
return test.level
|
||||
|
||||
|
||||
def adjust_white_balance_from_raw(
|
||||
camera: PiCamera, percentile: float = 99
|
||||
) -> Tuple[float, float]:
|
||||
"""Adjust the white balance in a single shot, based on the raw image.
|
||||
|
||||
NB if ``channels_from_raw_image`` is broken, this will go haywire.
|
||||
We should probably have better logic to verify the channels really
|
||||
are BGGR...
|
||||
"""
|
||||
blue, g1, g2, red = get_channel_percentiles(camera, percentile)
|
||||
green = (g1 + g2) / 2.0
|
||||
new_awb_gains = (green / red, green / blue)
|
||||
logging.info(
|
||||
f"Raw white point is R: {red} G: {green} B: {blue}, "
|
||||
f"setting AWB gains to ({new_awb_gains[0]:.2f}, "
|
||||
f"{new_awb_gains[1]:.2f})."
|
||||
)
|
||||
camera.awb_mode = "off"
|
||||
camera.awb_gains = new_awb_gains
|
||||
return new_awb_gains
|
||||
|
||||
|
||||
def channels_from_bayer_array(bayer_array: np.ndarray) -> np.ndarray:
|
||||
|
|
@ -95,6 +269,21 @@ def channels_from_bayer_array(bayer_array: np.ndarray) -> np.ndarray:
|
|||
return channels
|
||||
|
||||
|
||||
def get_channel_percentiles(camera: PiCamera, percentile: float) -> np.ndarray:
|
||||
"""Calculate the brightness percentile of the pixels in each channel
|
||||
|
||||
This is a number between -64 and 959 for each channel, because the
|
||||
camera takes 10-bit images (maximum=1023) and its zero level is set
|
||||
at 64 for denoising purposes (there's black level compensation built
|
||||
in, and to avoid skewing the noise, the black level is set as 64 to
|
||||
leave some room for negative values.
|
||||
"""
|
||||
with PiBayerArray(camera) as output:
|
||||
camera.capture(output, format="jpeg", bayer=True)
|
||||
channels = channels_from_bayer_array(output.array)
|
||||
return np.percentile(channels, percentile, axis=(1, 2)) - 64
|
||||
|
||||
|
||||
def lst_from_channels(channels: np.ndarray) -> np.ndarray:
|
||||
"""Given the 4 Bayer colour channels from a white image, generate a LST."""
|
||||
full_resolution: np.ndarray = np.array(
|
||||
|
|
@ -165,6 +354,20 @@ def lst_from_channels(channels: np.ndarray) -> np.ndarray:
|
|||
return lens_shading_table[::-1, :, :].copy()
|
||||
|
||||
|
||||
def lst_from_camera(camera: PiCamera) -> np.ndarray:
|
||||
"""Acquire a raw image and use it to calculate a lens shading table."""
|
||||
with PiBayerArray(camera) as a:
|
||||
camera.capture(a, format="jpeg", bayer=True)
|
||||
raw_image = a.array.copy()
|
||||
|
||||
# Now we need to calculate a lens shading table that would make this flat.
|
||||
# raw_image is a 3D array, with full resolution and 3 colour channels. No
|
||||
# de-mosaicing has been done, so 2/3 of the values are zero (3/4 for R and B
|
||||
# channels, 1/2 for green because there's twice as many green pixels).
|
||||
channels = channels_from_bayer_array(raw_image)
|
||||
return lst_from_channels(channels)
|
||||
|
||||
|
||||
def recalibrate_camera(camera: PiCamera):
|
||||
"""Reset the lens shading table and exposure settings.
|
||||
|
||||
|
|
@ -178,16 +381,7 @@ def recalibrate_camera(camera: PiCamera):
|
|||
camera.lens_shading_table = flat_lens_shading_table(camera)
|
||||
_ = rgb_image(camera) # for some reason the camera won't work unless I do this!
|
||||
|
||||
with PiBayerArray(camera) as a:
|
||||
camera.capture(a, format="jpeg", bayer=True)
|
||||
raw_image = a.array.copy()
|
||||
|
||||
# Now we need to calculate a lens shading table that would make this flat.
|
||||
# raw_image is a 3D array, with full resolution and 3 colour channels. No
|
||||
# de-mosaicing has been done, so 2/3 of the values are zero (3/4 for R and B
|
||||
# channels, 1/2 for green because there's twice as many green pixels).
|
||||
channels = channels_from_bayer_array(raw_image)
|
||||
lens_shading_table = lst_from_channels(channels)
|
||||
lens_shading_table = lst_from_camera(camera)
|
||||
|
||||
camera.lens_shading_table = lens_shading_table
|
||||
_ = rgb_image(camera)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import datetime
|
|||
import logging
|
||||
import time
|
||||
import uuid
|
||||
import marshmallow
|
||||
from functools import reduce
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
|
|
@ -361,7 +362,11 @@ class ScanExtension(BaseExtension):
|
|||
|
||||
class TileScanArgs(FullCaptureArgs):
|
||||
namemode = fields.String(missing="coordinates", example="coordinates")
|
||||
grid = fields.List(fields.Integer, missing=[3, 3, 3], example=[3, 3, 3])
|
||||
grid = fields.List(
|
||||
fields.Integer(validate=marshmallow.validate.Range(min=1)),
|
||||
missing=[3, 3, 3],
|
||||
example=[3, 3, 3],
|
||||
)
|
||||
style = fields.String(missing="raster")
|
||||
autofocus_dz = fields.Integer(missing=50)
|
||||
fast_autofocus = fields.Boolean(missing=False)
|
||||
|
|
|
|||
34
openflexure_microscope/api/static/package-lock.json
generated
34
openflexure_microscope/api/static/package-lock.json
generated
|
|
@ -3935,38 +3935,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "=3.1.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
}
|
||||
"follow-redirects": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"babel-eslint": {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
"@vue/cli-plugin-eslint": "^4.5.9",
|
||||
"@vue/cli-service": "^4.5.9",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"axios": "^0.19.2",
|
||||
"axios": "^0.21.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"eslint": "^6.8.0",
|
||||
|
|
|
|||
|
|
@ -194,6 +194,8 @@ import loggingContent from "./tabContentComponents/loggingContent.vue";
|
|||
import calibrationModal from "./modalComponents/calibrationModal.vue";
|
||||
import TabIcon from "./genericComponents/tabIcon.vue";
|
||||
|
||||
import { mapState } from "vuex";
|
||||
|
||||
// Export main app
|
||||
export default {
|
||||
name: "AppContent",
|
||||
|
|
@ -302,21 +304,22 @@ export default {
|
|||
return process.env.VUE_APP_ENABLE_IMJOY === "true";
|
||||
},
|
||||
|
||||
...mapState("imjoy", { imjoyTabs: "tabs" })
|
||||
// Map the tabs from ImJoy's store module so we can display them
|
||||
...mapState("imjoy", { imjoyTabs: "tabs" }),
|
||||
|
||||
// Map the setting for IHI's interface so we can watch it
|
||||
...mapState(["IHIEnabled"])
|
||||
},
|
||||
|
||||
watch: {
|
||||
// Update the interface when the IHI interface is enabled/disabled
|
||||
IHIEnabled: function(newValue) {
|
||||
this.updateTopTabs(newValue);
|
||||
}
|
||||
},
|
||||
|
||||
created: function() {
|
||||
if (this.$store.getters.ready) {
|
||||
// Detect local connection
|
||||
if (
|
||||
["localhost", "0.0.0.0", "127.0.0.1", "[::1]"].includes(
|
||||
window.location.hostname
|
||||
)
|
||||
) {
|
||||
this.$store.commit("changeDisableStream", true);
|
||||
this.$store.commit("changeAutoGpuPreview", true);
|
||||
this.$store.commit("changeTrackWindow", true);
|
||||
}
|
||||
// Update top tabs
|
||||
this.updateTopTabs(this.$store.state.IHIEnabled);
|
||||
// Update plugins
|
||||
|
|
@ -325,16 +328,6 @@ export default {
|
|||
this.startModals();
|
||||
});
|
||||
}
|
||||
|
||||
// Watch for host 'ready', then update status
|
||||
this.unwatchStoreFunction = this.$store.watch(
|
||||
state => {
|
||||
return state.IHIEnabled;
|
||||
},
|
||||
IHIEnabled => {
|
||||
this.updateTopTabs(IHIEnabled);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
|
@ -352,14 +345,6 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// Then we call that function here to unwatch
|
||||
if (this.unwatchStoreFunction) {
|
||||
this.unwatchStoreFunction();
|
||||
this.unwatchStoreFunction = null;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateTopTabs: function(IHIEnabled) {
|
||||
if (IHIEnabled) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,36 @@
|
|||
<template>
|
||||
<div>
|
||||
<form class="uk-form-stacked" @submit.prevent="overrideAPIHost">
|
||||
<form
|
||||
class="uk-form-stacked"
|
||||
action=""
|
||||
method="GET"
|
||||
@submit="overrideAPIHost"
|
||||
>
|
||||
<label class="uk-form-label">Override API origin</label>
|
||||
<input v-model="newOrigin" class="uk-input" type="text" />
|
||||
<input
|
||||
v-model="newOrigin"
|
||||
name="overrideOrigin"
|
||||
class="uk-input"
|
||||
type="text"
|
||||
/>
|
||||
<label class="uk-form-label">
|
||||
<input
|
||||
v-model="reloadWhenOverridingOrigin"
|
||||
class="uk-input uk-checkbox"
|
||||
type="checkbox"
|
||||
/>
|
||||
Reload web app with new origin
|
||||
</label>
|
||||
<button class="uk-button uk-button-default uk-margin-small">
|
||||
Apply
|
||||
</button>
|
||||
</form>
|
||||
<form class="uk-form-stacked" @submit.prevent="resetTour">
|
||||
<label class="uk-form-label">Re-run tour on next load</label>
|
||||
<button class="uk-button uk-button-default uk-margin-small">
|
||||
Reset
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -19,22 +43,35 @@ export default {
|
|||
|
||||
data: function() {
|
||||
return {
|
||||
newOrigin: this.$store.state.origin
|
||||
newOrigin: this.$store.state.origin,
|
||||
reloadWhenOverridingOrigin: true
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (localStorage.overrideOrigin){
|
||||
if (localStorage.overrideOrigin) {
|
||||
this.newOrigin = localStorage.overrideOrigin;
|
||||
}else{
|
||||
} else {
|
||||
this.newOrigin = "http://microscope.local:5000";
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
overrideAPIHost: function() {
|
||||
this.$store.commit("changeOrigin", this.newOrigin);
|
||||
localStorage.overrideOrigin = this.newOrigin
|
||||
overrideAPIHost: function(event) {
|
||||
// Save the origin override, so that if we reload the web app, you can easily
|
||||
localStorage.overrideOrigin = this.newOrigin;
|
||||
|
||||
// If we have elected not to reload the interface, just update the origin
|
||||
// in the store. Otherwise, the form's default action will do the job for us.
|
||||
// TODO: preserve other query parameters when reloading
|
||||
if (!this.reloadWhenOverridingOrigin) {
|
||||
this.$store.commit("changeOrigin", this.newOrigin);
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
resetTour: function() {
|
||||
// Make the introduction tour run next time the app loads
|
||||
this.setLocalStorageObj("completedTour", false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@
|
|||
v-model="scanStepSize.z"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionZx"
|
||||
name="inputPositionZ"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -172,6 +172,7 @@
|
|||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionX"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -186,6 +187,7 @@
|
|||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionY"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -199,7 +201,8 @@
|
|||
v-model="scanSteps.z"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
name="inputPositionZx"
|
||||
name="inputPositionZ"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="uk-grid uk-grid-divider uk-child-width-expand" uk-grid>
|
||||
<div class="uk-width-large">
|
||||
<h3>Manual camera settings</h3>
|
||||
<form @submit.prevent="applySettingsRequest">
|
||||
<form @submit.prevent="applySettingsRequest" >
|
||||
<div class="uk-margin-small-bottom">
|
||||
<ul uk-accordion="multiple: true">
|
||||
<li class="uk-open">
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
v-model="picamera.analog_gain"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
step="0.0000000000000001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -45,7 +45,29 @@
|
|||
v-model="picamera.digital_gain"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
step="0.0000000000000001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="picamera.awb_gains !== undefined">
|
||||
<label class="uk-form-label" for="form-stacked-text" >
|
||||
White Balance gains
|
||||
</label>
|
||||
<div class="uk-form-controls">
|
||||
<label class="uk-form-label" >R:</label>
|
||||
<input
|
||||
v-model="picamera.awb_gains[0]"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.0000000000000001"
|
||||
/>
|
||||
<label class="uk-form-label" >B:</label>
|
||||
<input
|
||||
v-model="picamera.awb_gains[1]"
|
||||
class="uk-input uk-form-small"
|
||||
type="number"
|
||||
step="0.0000000000000001"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -194,7 +216,8 @@ export default {
|
|||
shutter_speed: undefined,
|
||||
analog_gain: undefined,
|
||||
digital_gain: undefined,
|
||||
framerate: undefined
|
||||
framerate: undefined,
|
||||
awb_gains: undefined
|
||||
},
|
||||
mjpeg_bitrate: undefined,
|
||||
stream_resolution: undefined,
|
||||
|
|
@ -244,6 +267,7 @@ export default {
|
|||
this.picamera.digital_gain = cameraSettings.picamera.digital_gain;
|
||||
this.picamera.shutter_speed = cameraSettings.picamera.shutter_speed;
|
||||
this.picamera.framerate = cameraSettings.picamera.framerate;
|
||||
this.picamera.awb_gains = cameraSettings.picamera.awb_gains;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
@ -263,7 +287,11 @@ export default {
|
|||
shutter_speed: parseFloat(this.picamera.shutter_speed),
|
||||
analog_gain: parseFloat(this.picamera.analog_gain),
|
||||
digital_gain: parseFloat(this.picamera.digital_gain),
|
||||
framerate: parseInt(this.picamera.framerate)
|
||||
framerate: parseInt(this.picamera.framerate),
|
||||
awb_gains: [
|
||||
parseFloat(this.picamera.awb_gains[0]),
|
||||
parseFloat(this.picamera.awb_gains[1]),
|
||||
],
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,44 @@
|
|||
'Start recalibration? This may take a while, and the microscope will be locked during this time.'
|
||||
"
|
||||
:submit-url="recalibrationLinks.recalibrate.href"
|
||||
:submit-label="'Auto-Calibrate'"
|
||||
:submit-label="'Full Auto-Calibrate'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
</div>
|
||||
<div v-if="'auto_exposure_from_raw' in recalibrationLinks" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="false"
|
||||
:submit-url="recalibrationLinks.auto_exposure_from_raw.href"
|
||||
:submit-label="'Auto gain & shutter speed'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
</div>
|
||||
<div v-if="'auto_white_balance_from_raw' in recalibrationLinks" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="false"
|
||||
:submit-url="recalibrationLinks.auto_white_balance_from_raw.href"
|
||||
:submit-label="'Auto white balance'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
</taskSubmitter>
|
||||
</div>
|
||||
<div v-if="'auto_lens_shading_table' in recalibrationLinks" class="uk-margin-small">
|
||||
<taskSubmitter
|
||||
:can-terminate="false"
|
||||
:requires-confirmation="true"
|
||||
:confirmation-message="
|
||||
'Is the microscope looking at an evenly illuminated, empty field of view? ' +
|
||||
'If not, the current image will show through in any images captured afterwards.'
|
||||
"
|
||||
:submit-url="recalibrationLinks.auto_lens_shading_table.href"
|
||||
:submit-label="'Auto flat field correction'"
|
||||
@response="onRecalibrateResponse"
|
||||
@error="modalError"
|
||||
>
|
||||
|
|
@ -22,15 +59,7 @@
|
|||
class="uk-button uk-button-danger uk-width-1-1"
|
||||
@click="flattenLensShadingTableRequest"
|
||||
>
|
||||
Disable flat-field correction
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="'delete_lens_shading_table' in recalibrationLinks"
|
||||
class="uk-button uk-button-danger uk-margin-small-top uk-width-1-1"
|
||||
@click="deleteLensShadingTableRequest"
|
||||
>
|
||||
Adaptive flat-field correction
|
||||
Disable flat field correction
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ export default {
|
|||
},
|
||||
set(value) {
|
||||
this.$store.commit("changeIHIEnabled", value);
|
||||
this.$root.$emit("globalSafeTogglePreview", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -73,8 +73,11 @@ export default {
|
|||
return this.$store.state.autoGpuPreview;
|
||||
},
|
||||
set(value) {
|
||||
// NB the stream viewer watches the store, and is
|
||||
// responsible for making the request that switches
|
||||
// GPU preview on/off
|
||||
// see streamContent.vue
|
||||
this.$store.commit("changeAutoGpuPreview", value);
|
||||
this.$root.$emit("globalSafeTogglePreview", value);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -86,6 +89,51 @@ export default {
|
|||
this.$store.commit("changeTrackWindow", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
// Cache the stream settings to local storage for persistence
|
||||
// (the next 3 functions all relate to this)
|
||||
disableStream: function(newValue) {
|
||||
console.log(
|
||||
`disableStream updated to ${newValue} and saved in local storage`
|
||||
);
|
||||
this.setLocalStorageObj("disableStream", newValue);
|
||||
},
|
||||
autoGpuPreview: function(newValue) {
|
||||
console.log(
|
||||
`GPU preview updated to ${newValue} and saved in local storage`
|
||||
);
|
||||
this.setLocalStorageObj("autoGpuPreview", newValue);
|
||||
},
|
||||
trackWindow: function(newValue) {
|
||||
console.log(
|
||||
`trackWindow updated to ${newValue} and saved in local storage`
|
||||
);
|
||||
this.setLocalStorageObj("trackWindow", newValue);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
// Apply sensible defaults for stream settings, depending on
|
||||
// whether we're connecting locally or remotely, respecting
|
||||
// the settings that were cached previously.
|
||||
const localMode = ["localhost", "0.0.0.0", "127.0.0.1", "[::1]"].includes(
|
||||
window.location.hostname
|
||||
);
|
||||
const localDefaults = {
|
||||
disableStream: true,
|
||||
autoGpuPreview: true,
|
||||
trackWindow: true
|
||||
};
|
||||
for (let k in localDefaults) {
|
||||
if (localStorage.getItem(k) !== null) {
|
||||
this[k] = this.getLocalStorageObj(k);
|
||||
} else if (localMode) {
|
||||
console.log(`${k} set to default value for a local connection`);
|
||||
this[k] = localDefaults[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@ export default {
|
|||
},
|
||||
settingsUri: function() {
|
||||
return `${this.$store.getters.baseUri}/api/v2/instrument/settings`;
|
||||
},
|
||||
autoGpuPreview: function() {
|
||||
return this.$store.state.autoGpuPreview;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
autoGpuPreview: function(newValue) {
|
||||
// When the GPU preview setting in the store changes, update the server
|
||||
this.safePreviewRequest(newValue);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -77,9 +87,6 @@ export default {
|
|||
this.$root.$on("globalTogglePreview", state => {
|
||||
this.previewRequest(state);
|
||||
});
|
||||
this.$root.$on("globalSafeTogglePreview", state => {
|
||||
this.safePreviewRequest(state);
|
||||
});
|
||||
// A global signal listener to flash the stream element
|
||||
this.$root.$on("globalFlashStream", () => {
|
||||
this.flashStream();
|
||||
|
|
@ -97,7 +104,7 @@ export default {
|
|||
|
||||
created: function() {
|
||||
// Send a request to start/stop GPU preview based on global setting
|
||||
this.safePreviewRequest(this.$store.state.autoGpuPreview);
|
||||
this.safePreviewRequest(this.autoGpuPreview);
|
||||
},
|
||||
|
||||
beforeDestroy: function() {
|
||||
|
|
|
|||
|
|
@ -61,12 +61,25 @@ const moduleImjoy = {
|
|||
getters: {}
|
||||
};
|
||||
|
||||
function getOriginFromLocation() {
|
||||
// This will default to the same origin that's serving
|
||||
// the web app - but can be overridden by the URL.
|
||||
// See also devTools.vue which can change the origin.
|
||||
let url = new URL(window.location.href);
|
||||
let origin = url.searchParams.get("overrideOrigin");
|
||||
if (origin) {
|
||||
return origin;
|
||||
} else {
|
||||
return url.origin;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
imjoy: moduleImjoy
|
||||
},
|
||||
state: {
|
||||
origin: window.location.origin,
|
||||
origin: getOriginFromLocation(),
|
||||
available: false,
|
||||
waiting: false,
|
||||
error: "",
|
||||
|
|
|
|||
1592
poetry.lock
generated
1592
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -45,6 +45,7 @@ isort = "isort ."
|
|||
pylint = "pylint openflexure_microscope"
|
||||
mypy = "mypy --cobertura-xml-report openflexure_microscope openflexure_microscope"
|
||||
test = "pytest ."
|
||||
serve = "python -m openflexure_microscope.api.app"
|
||||
|
||||
format = ["black", "isort"]
|
||||
lint = ["pylint", "mypy"]
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -22,7 +22,7 @@ with open(path.join(here, "README.md"), encoding="utf-8") as f:
|
|||
|
||||
setup(
|
||||
name="openflexure-microscope-server",
|
||||
version="2.10.0b0",
|
||||
version="2.10.0b1",
|
||||
description="Python module, and Flask-based web API, to run the OpenFlexure Microscope.",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue