diff --git a/Pipfile b/Pipfile index b89c56f..4571055 100644 --- a/Pipfile +++ b/Pipfile @@ -6,6 +6,7 @@ name = "pypi" [packages] [dev-packages] +ipykernel = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index 7b99c4d..1d5f1e4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "68043c91911727f5087a664d0a0a6dc5875e83b6aa72e10f448c8bed5d5d77b7" + "sha256": "1369ab74af2377026598def7ac710921ed5c32a02c37f2266378cd43c5b0f9fc" }, "pipfile-spec": 6, "requires": { @@ -17,5 +17,368 @@ ] }, "default": {}, - "develop": {} + "develop": { + "appnope": { + "hashes": [ + "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", + "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c" + ], + "markers": "platform_system == 'Darwin'", + "version": "==0.1.4" + }, + "asttokens": { + "hashes": [ + "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", + "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0" + ], + "version": "==2.4.1" + }, + "comm": { + "hashes": [ + "sha256:0bc91edae1344d39d3661dcbc36937181fdaddb304790458f8b044dbc064b89a", + "sha256:87928485c0dfc0e7976fd89fc1e187023cf587e7c353e4a9b417555b44adf021" + ], + "markers": "python_version >= '3.8'", + "version": "==0.2.1" + }, + "debugpy": { + "hashes": [ + "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb", + "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146", + "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8", + "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242", + "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0", + "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741", + "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539", + "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23", + "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3", + "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39", + "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd", + "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9", + "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace", + "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42", + "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0", + "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7", + "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e", + "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234", + "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98", + "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703", + "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42", + "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099" + ], + "markers": "python_version >= '3.8'", + "version": "==1.8.1" + }, + "decorator": { + "hashes": [ + "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", + "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" + ], + "markers": "python_version >= '3.5'", + "version": "==5.1.1" + }, + "executing": { + "hashes": [ + "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147", + "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc" + ], + "markers": "python_version >= '3.5'", + "version": "==2.0.1" + }, + "ipykernel": { + "hashes": [ + "sha256:5aa086a4175b0229d4eca211e181fb473ea78ffd9869af36ba7694c947302a21", + "sha256:e14c250d1f9ea3989490225cc1a542781b095a18a19447fcf2b5eaf7d0ac5bd2" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.29.3" + }, + "ipython": { + "hashes": [ + "sha256:2dcaad9049f9056f1fef63514f176c7d41f930daa78d05b82a176202818f2c14", + "sha256:3c86f284c8f3d8f2b6c662f885c4889a91df7cd52056fd02b7d8d6195d7f56e9" + ], + "markers": "python_version >= '3.10'", + "version": "==8.22.2" + }, + "jedi": { + "hashes": [ + "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", + "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0" + ], + "markers": "python_version >= '3.6'", + "version": "==0.19.1" + }, + "jupyter-client": { + "hashes": [ + "sha256:0642244bb83b4764ae60d07e010e15f0e2d275ec4e918a8f7b80fbbef3ca60c7", + "sha256:909c474dbe62582ae62b758bca86d6518c85234bdee2d908c778db6d72f39d99" + ], + "markers": "python_version >= '3.8'", + "version": "==8.6.0" + }, + "jupyter-core": { + "hashes": [ + "sha256:c65c82126453a723a2804aa52409930434598fd9d35091d63dfb919d2b765bb7", + "sha256:de61a9d7fc71240f688b2fb5ab659fbb56979458dc66a71decd098e03c79e218" + ], + "markers": "python_version >= '3.8'", + "version": "==5.7.1" + }, + "matplotlib-inline": { + "hashes": [ + "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", + "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" + ], + "markers": "python_version >= '3.5'", + "version": "==0.1.6" + }, + "nest-asyncio": { + "hashes": [ + "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", + "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c" + ], + "markers": "python_version >= '3.5'", + "version": "==1.6.0" + }, + "packaging": { + "hashes": [ + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + ], + "markers": "python_version >= '3.7'", + "version": "==23.2" + }, + "parso": { + "hashes": [ + "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", + "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" + ], + "markers": "python_version >= '3.6'", + "version": "==0.8.3" + }, + "pexpect": { + "hashes": [ + "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", + "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" + ], + "markers": "sys_platform != 'win32' and sys_platform != 'emscripten'", + "version": "==4.9.0" + }, + "platformdirs": { + "hashes": [ + "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", + "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" + ], + "markers": "python_version >= '3.8'", + "version": "==4.2.0" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d", + "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.0.43" + }, + "psutil": { + "hashes": [ + "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d", + "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73", + "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8", + "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2", + "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e", + "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36", + "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7", + "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c", + "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee", + "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421", + "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf", + "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81", + "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0", + "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631", + "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4", + "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==5.9.8" + }, + "ptyprocess": { + "hashes": [ + "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", + "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" + ], + "version": "==0.7.0" + }, + "pure-eval": { + "hashes": [ + "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", + "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" + ], + "version": "==0.2.2" + }, + "pygments": { + "hashes": [ + "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", + "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" + ], + "markers": "python_version >= '3.7'", + "version": "==2.17.2" + }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.9.0.post0" + }, + "pyzmq": { + "hashes": [ + "sha256:004ff469d21e86f0ef0369717351073e0e577428e514c47c8480770d5e24a565", + "sha256:00a06faa7165634f0cac1abb27e54d7a0b3b44eb9994530b8ec73cf52e15353b", + "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979", + "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1", + "sha256:019744b99da30330798bb37df33549d59d380c78e516e3bab9c9b84f87a9592f", + "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d", + "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee", + "sha256:07cd61a20a535524906595e09344505a9bd46f1da7a07e504b315d41cd42eb07", + "sha256:0806175f2ae5ad4b835ecd87f5f85583316b69f17e97786f7443baaf54b9bb98", + "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886", + "sha256:0dabfb10ef897f3b7e101cacba1437bd3a5032ee667b7ead32bbcdd1a8422fe7", + "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75", + "sha256:0f513130c4c361201da9bc69df25a086487250e16b5571ead521b31ff6b02220", + "sha256:0f97bc2f1f13cb16905a5f3e1fbdf100e712d841482b2237484360f8bc4cb3d7", + "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a", + "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314", + "sha256:16b726c1f6c2e7625706549f9dbe9b06004dfbec30dbed4bf50cbdfc73e5b32a", + "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27", + "sha256:1b9b1f2ad6498445a941d9a4fee096d387fee436e45cc660e72e768d3d8ee611", + "sha256:1ec23bd7b3a893ae676d0e54ad47d18064e6c5ae1fadc2f195143fb27373f7f6", + "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6", + "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9", + "sha256:2c6441e0398c2baacfe5ba30c937d274cfc2dc5b55e82e3749e333aabffde561", + "sha256:2c9a79f1d2495b167119d02be7448bfba57fad2a4207c4f68abc0bab4b92925b", + "sha256:2e2713ef44be5d52dd8b8e2023d706bf66cb22072e97fc71b168e01d25192755", + "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e", + "sha256:3516e0b6224cf6e43e341d56da15fd33bdc37fa0c06af4f029f7d7dfceceabbc", + "sha256:359f7f74b5d3c65dae137f33eb2bcfa7ad9ebefd1cab85c935f063f1dbb245cc", + "sha256:39b1067f13aba39d794a24761e385e2eddc26295826530a8c7b6c6c341584289", + "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d", + "sha256:3c53687dde4d9d473c587ae80cc328e5b102b517447456184b485587ebd18b62", + "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642", + "sha256:4345c9a27f4310afbb9c01750e9461ff33d6fb74cd2456b107525bbeebcb5be3", + "sha256:45999e7f7ed5c390f2e87ece7f6c56bf979fb213550229e711e45ecc7d42ccb8", + "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0", + "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4", + "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097", + "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b", + "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181", + "sha256:518efd91c3d8ac9f9b4f7dd0e2b7b8bf1a4fe82a308009016b07eaa48681af82", + "sha256:55875492f820d0eb3417b51d96fea549cde77893ae3790fd25491c5754ea2f68", + "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08", + "sha256:5dde6751e857910c1339890f3524de74007958557593b9e7e8c5f01cd919f8a7", + "sha256:5e319ed7d6b8f5fad9b76daa0a68497bc6f129858ad956331a5835785761e003", + "sha256:5edac3f57c7ddaacdb4d40f6ef2f9e299471fc38d112f4bc6d60ab9365445fb0", + "sha256:6cc0020b74b2e410287e5942e1e10886ff81ac77789eb20bec13f7ae681f0fdd", + "sha256:6dd0d50bbf9dca1d0bdea219ae6b40f713a3fb477c06ca3714f208fd69e16fd8", + "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840", + "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8", + "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe", + "sha256:7b6d09a8962a91151f0976008eb7b29b433a560fde056ec7a3db9ec8f1075438", + "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e", + "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d", + "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c", + "sha256:8807c87fa893527ae8a524c15fc505d9950d5e856f03dae5921b5e9aa3b8783b", + "sha256:889370d5174a741a62566c003ee8ddba4b04c3f09a97b8000092b7ca83ec9c49", + "sha256:8b14c75979ce932c53b79976a395cb2a8cd3aaf14aef75e8c2cb55a330b9b49d", + "sha256:8c5f80e578427d4695adac6fdf4370c14a2feafdc8cb35549c219b90652536ae", + "sha256:8e9f3fabc445d0ce320ea2c59a75fe3ea591fdbdeebec5db6de530dd4b09412e", + "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226", + "sha256:94504ff66f278ab4b7e03e4cba7e7e400cb73bfa9d3d71f58d8972a8dc67e7a6", + "sha256:967668420f36878a3c9ecb5ab33c9d0ff8d054f9c0233d995a6d25b0e95e1b6b", + "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3", + "sha256:99a6b36f95c98839ad98f8c553d8507644c880cf1e0a57fe5e3a3f3969040882", + "sha256:9a18fff090441a40ffda8a7f4f18f03dc56ae73f148f1832e109f9bffa85df15", + "sha256:9add2e5b33d2cd765ad96d5eb734a5e795a0755f7fc49aa04f76d7ddda73fd70", + "sha256:a793ac733e3d895d96f865f1806f160696422554e46d30105807fdc9841b9f7d", + "sha256:a86c2dd76ef71a773e70551a07318b8e52379f58dafa7ae1e0a4be78efd1ff16", + "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05", + "sha256:ac170e9e048b40c605358667aca3d94e98f604a18c44bdb4c102e67070f3ac9b", + "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737", + "sha256:b8c8a419dfb02e91b453615c69568442e897aaf77561ee0064d789705ff37a92", + "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348", + "sha256:bef02cfcbded83473bdd86dd8d3729cd82b2e569b75844fb4ea08fee3c26ae41", + "sha256:c0b5ca88a8928147b7b1e2dfa09f3b6c256bc1135a1338536cbc9ea13d3b7add", + "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b", + "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7", + "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d", + "sha256:d1b604734bec94f05f81b360a272fc824334267426ae9905ff32dc2be433ab96", + "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e", + "sha256:db36c27baed588a5a8346b971477b718fdc66cf5b80cbfbd914b4d6d355e44e2", + "sha256:df0c7a16ebb94452d2909b9a7b3337940e9a87a824c4fc1c7c36bb4404cb0cde", + "sha256:e10a4b5a4b1192d74853cc71a5e9fd022594573926c2a3a4802020360aa719d8", + "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4", + "sha256:e690145a8c0c273c28d3b89d6fb32c45e0d9605b2293c10e650265bf5c11cfec", + "sha256:ea1608dd169da230a0ad602d5b1ebd39807ac96cae1845c3ceed39af08a5c6df", + "sha256:ea253b368eb41116011add00f8d5726762320b1bda892f744c91997b65754d73", + "sha256:eb7e49a17fb8c77d3119d41a4523e432eb0c6932187c37deb6fbb00cc3028088", + "sha256:ef12e259e7bc317c7597d4f6ef59b97b913e162d83b421dd0db3d6410f17a244", + "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537", + "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6", + "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872", + "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30" + ], + "markers": "python_version >= '3.6'", + "version": "==25.1.2" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "stack-data": { + "hashes": [ + "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", + "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695" + ], + "version": "==0.6.3" + }, + "tornado": { + "hashes": [ + "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0", + "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63", + "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263", + "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052", + "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f", + "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee", + "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78", + "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579", + "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212", + "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e", + "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2" + ], + "markers": "python_version >= '3.8'", + "version": "==6.4" + }, + "traitlets": { + "hashes": [ + "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74", + "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e" + ], + "markers": "python_version >= '3.8'", + "version": "==5.14.1" + }, + "wcwidth": { + "hashes": [ + "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", + "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" + ], + "version": "==0.2.13" + } + } } diff --git a/dev/dev.ipynb b/dev/dev.ipynb index 0cfba8e..f1396c6 100644 --- a/dev/dev.ipynb +++ b/dev/dev.ipynb @@ -12590,6 +12590,26 @@ "a.x" ] }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "7600" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "38*200" + ] + }, { "cell_type": "code", "execution_count": 37, @@ -12672,6 +12692,26 @@ "np.all(d2.x == d3.x), np.all(d2.y == d3.y)" ] }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2.5" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "0.15/0.06" + ] + }, { "cell_type": "code", "execution_count": 7, @@ -13058,6 +13098,183 @@ "ts_1000 = np.linspace(0, 1, 1000)\n", "data_1000 = np.sin(2 * np.pi * ts_1000)\n" ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1.70621755e+09, 1.70621755e+09, 1.70621755e+09, 1.70621757e+09,\n", + " 1.70621759e+09, 1.70621759e+09, 1.70621759e+09])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_tstamps" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.2143950399286514" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.random.uniform(0, 0.5)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2m2024-03-12T15:28:48.767775Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading recording from: ../tests/test_data/2024-01-25_22-19-10_test-f96b6e36/\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.769442Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading recording info\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.771087Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading calibration data\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.773302Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading raw time (ns) files\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.775611Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading gaze data\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.777397Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading IMU data\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.875818Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading scene camera video\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.906987Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading eye camera video\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.913535Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading events \u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.914691Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Parsing unique events\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "\u001b[2m2024-03-12T15:28:48.915374Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Finished loading recording.\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35mload\u001b[0m\n", + "{'gaze': array([[ True, True, True, True],\n", + " [ True, True, True, True],\n", + " [ True, True, True, True],\n", + " [ True, True, True, True]])}\n", + "all equal gaze shifted (linear interp): True\n" + ] + } + ], + "source": [ + "import cv2\n", + "import numpy as np\n", + "import random\n", + "\n", + "import pupil_labs.neon_recording as nr\n", + "\n", + "rec = nr.load('../tests/test_data/2024-01-25_22-19-10_test-f96b6e36/')\n", + "\n", + "gaze = rec.gaze\n", + "imus = rec.imu\n", + "eye = rec.eye\n", + "scene = rec.scene\n", + "\n", + "tstamps = gaze.ts\n", + "test_tstamps = [tstamps[100], tstamps[150], tstamps[200], tstamps[len(tstamps)//2], tstamps[-400], tstamps[-203], tstamps[-200]]\n", + "for ic in range(len(test_tstamps)):\n", + "\ttest_tstamps[ic] = test_tstamps[ic] + np.random.uniform(0, 0.5)\n", + "\n", + "shuffled_test_tstamps = random.sample(test_tstamps, len(test_tstamps))\n", + "\n", + "gaze_samps_shifted = {\n", + " 'np.interp': [],\n", + " 'np.interp_shuffled': [],\n", + " 'rob.interp': [],\n", + " 'rob.interp_shuffled': [],\n", + "}\n", + "\n", + "# np.interp approach\n", + "gaze_samps_shifted['np.interp'] = gaze.sample(test_tstamps, method=\"linear\")\n", + "gaze_samps_shifted['np.interp_shuffled'] = gaze.sample(shuffled_test_tstamps, method=\"linear\")\n", + "\n", + "# rob approach\n", + "gaze_samps_shifted['rob.interp'] = gaze.sample_rob_interp(test_tstamps)\n", + "gaze_samps_shifted['rob.interp_shuffled'] = gaze.sample_rob_interp(shuffled_test_tstamps)\n", + "\n", + "all_equal_interp_shifted = {\n", + " 'gaze': np.zeros((4, 4), dtype=bool),\n", + "}\n", + "kc = 0\n", + "for k in gaze_samps_shifted:\n", + " jc = 0\n", + " for j in gaze_samps_shifted:\n", + " all_equal_interp_shifted['gaze'][kc, jc] = np.all(gaze_samps_shifted[k] == gaze_samps_shifted[j])\n", + "\n", + " jc += 1\n", + "\n", + " kc += 1\n", + "\n", + "print(all_equal_interp_shifted)\n", + "print('all equal gaze shifted (linear interp):', np.all(all_equal_interp_shifted['gaze']))\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "rec.array([(805.25998137, 756.99365861, 1.70621755e+09, 3.55813646),\n", + " (794.2766782 , 765.21001451, 1.70621755e+09, 3.73488307),\n", + " (801.59493555, 710.08808104, 1.70621755e+09, 3.91472697),\n", + " (930.27649807, 823.59277847, 1.70621757e+09, 21.6195159 ),\n", + " (755.91415717, 858.2909549 , 1.70621759e+09, 38.59534764),\n", + " (894.48095481, 1122.28195571, 1.70621759e+09, 39.44808292),\n", + " (884.4642695 , 1098.74650958, 1.70621759e+09, 39.58002234)],\n", + " dtype=[('x', ' self.ts[-1] + + + def sample_one(self, ts_wanted: float, dt: float = 0.01, method="nearest"): log.debug("NeonRecording: Sampling one timestamp.") # in case they pass multiple timestamps # see note at https://numpy.org/doc/stable/reference/generated/numpy.isscalar.html if not np.ndim(ts_wanted) == 0: raise ValueError("This function can only sample a single timestamp. Use 'sample' for multiple timestamps.") + + if self._ts_oob(ts_wanted): + return None - diffs = np.abs(self.ts - ts_wanted) + if method == "nearest": + datum = self.data[np.searchsorted(self.ts, ts_wanted)] + if np.abs(datum.ts - ts_wanted) < dt: + return datum + else: + return None + elif method == "nearest_rob": + diffs = np.abs(self.ts - ts_wanted) - if np.any(diffs < dt): - idx = int(np.argmin(diffs)) - return self.data[idx] - else: - return None + if np.any(diffs < dt): + idx = int(np.argmin(diffs)) + return self.data[idx] + else: + return None + elif method == "linear": + datum = self.interp_data([ts_wanted], "linear") + if np.abs(datum.ts - ts_wanted) < dt: + return datum + else: + return None @abc.abstractmethod @@ -60,20 +80,30 @@ def sample(self, tstamps, method="nearest"): log.debug("NeonRecording: Sampling timestamps.") if len(tstamps) == 1: - if method == "nearest": - return self.data[np.searchsorted(self.ts, tstamps)] - elif method == "linear": - return self.interp_data([tstamps], "linear") + if self._ts_oob(tstamps): + return None + + if method == "nearest" or method == "linear": + return self.interp_data([tstamps], method) + elif method == "nearest_rob": + return self.sample_rob(tstamps) else: sorted_ts = np.sort(tstamps) return self.interp_data(sorted_ts, method) + # rob testing linear interpolation by hand def sample_rob_interp(self, tstamps): log.debug("NeonRecording: Sampling with (rob) linear interpolation.") - # rob testing linear interpolation by hand - sorted_ts = np.sort(tstamps) + if len(tstamps) == 1: + if self._ts_oob(tstamps): + return None + + sorted_ts = [tstamps] + else: + # for that perculiar user who does not send in the timestamps in order ;-) + sorted_ts = np.sort(tstamps) ds = self.ts - sorted_ts[:, np.newaxis] closest_idxs = np.argmin(np.abs(ds), axis=1) @@ -84,60 +114,52 @@ def sample_rob_interp(self, tstamps): d = ts - self.ts[ix] if np.sign(d) == +1: - left_ts = self.ts[ix] - right_ts = self.ts[ix+1] - - left_data = self.data[ix] - right_data = self.data[ix+1] + if ix == len(self.ts) - 1: + interp_data[ic] = self.data[ix] + continue + else: + left_ts = self.ts[ix] + right_ts = self.ts[ix+1] + + left_data = self.data[ix] + right_data = self.data[ix+1] else: - left_ts = self.ts[ix-1] - right_ts = self.ts[ix] + if ix == 0: + interp_data[ic] = self.data[ix] + continue + else: + left_ts = self.ts[ix-1] + right_ts = self.ts[ix] - left_data = self.data[ix-1] - right_data = self.data[ix] + left_data = self.data[ix-1] + right_data = self.data[ix] A = (ts - left_ts)/(right_ts - left_ts) - interp_data.x[ic] = left_data.x + A * (right_data.x - left_data.x) - interp_data.y[ic] = left_data.y + A * (right_data.y - left_data.y) - interp_data.ts[ic] = left_data.ts + A * (right_data.ts - left_data.ts) - interp_data.ts_rel[ic] = left_data.ts_rel + A * (right_data.ts_rel - left_data.ts_rel) + if self.name == 'gaze': + interp_data.x[ic] = left_data.x + A * (right_data.x - left_data.x) + interp_data.y[ic] = left_data.y + A * (right_data.y - left_data.y) + interp_data.ts[ic] = left_data.ts + A * (right_data.ts - left_data.ts) + interp_data.ts_rel[ic] = left_data.ts_rel + A * (right_data.ts_rel - left_data.ts_rel) + elif self.name == 'imu': + # just testing gaze is enough + continue return interp_data - def sample_mpk(self, tstamps): - log.debug("NeonRecording: Sampling (mpk) timestamp windows.") - - # Use np.searchsorted to find the insertion points for all bounds at once - start_indices = np.searchsorted(self.ts, tstamps, side='left') - # Shift to align with start_indices for slicing - end_indices = np.searchsorted(self.ts, tstamps, side='right')[1:] - - # Initialize a list to hold the slice indices - closest_idxs = [] - for start, end in zip(start_indices, np.append(end_indices, len(self.ts))): - # Generate the range of indices for this slice - if start != end: - indices = np.arange(start, end) - closest_idxs.append(indices) - - # the last one is not needed because we only want the bounded matches - closest_idxs = closest_idxs[:-1] - closest_idxs = np.array([idx.flatten() for idx in closest_idxs if len(idx) > 0]) - closest_idxs = np.unique(closest_idxs) - - return self.data[closest_idxs] - - def sample_rob_broadcast(self, tstamps): log.debug("NeonRecording: Sampling (rob - broadcast) nearest timestamps.") if len(tstamps) == 1: - return self.data[np.searchsorted(self.ts, tstamps)] - - # for that perculiar user who does not send in the timestamps in order ;-) - sorted_tses = np.sort(tstamps) + if self._ts_oob(tstamps): + return None + + sorted_tses = [tstamps] + else: + # for that perculiar user who does not send in the timestamps in order ;-) + sorted_tses = np.sort(tstamps) + ds = self.ts - sorted_tses[:, np.newaxis] closest_idxs = np.argmin(np.abs(ds), axis=1) @@ -148,38 +170,13 @@ def sample_rob(self, tstamps): log.debug("NeonRecording: Sampling (rob) nearest timestamps.") if len(tstamps) == 1: - return self.data[np.searchsorted(self.ts, tstamps)] - - # for that perculiar user who does not send in the timestamps in order ;-) - sorted_tses = np.sort(tstamps) - closest_idxs = [np.argmin(np.abs(self.ts - curr_ts)) for curr_ts in sorted_tses] - - return self.data[closest_idxs] - - - def sample_rob_orig(self, tstamps): - log.debug("NeonRecording: Sampling (rob) timestamp windows.") + if self._ts_oob(tstamps): + return None - if len(tstamps) == 1: - return self.data[np.searchsorted(self.ts, tstamps)] - - # for that perculiar user who does not send in the timestamps in order ;-) - sorted_tses = np.sort(tstamps) - - closest_idxs = [] - for tc in range(1, len(sorted_tses)): - prior_ts = sorted_tses[tc - 1] - curr_ts = sorted_tses[tc] - - if tc == 1: - bounded_tstamps = self.ts[(self.ts >= prior_ts) & (self.ts <= curr_ts)] - else: - bounded_tstamps = self.ts[(self.ts > prior_ts) & (self.ts <= curr_ts)] - - # just always take the one that is closest to the current timestamp - closest_ts = bounded_tstamps[-1] - - # this has the feeling of suboptimal - closest_idxs.append(int(np.where(self.ts == closest_ts)[0][0])) + sorted_tses = [tstamps] + else: + # for that perculiar user who does not send in the timestamps in order ;-) + sorted_tses = np.sort(tstamps) + closest_idxs = [np.argmin(np.abs(self.ts - curr_ts)) for curr_ts in sorted_tses] return self.data[closest_idxs] diff --git a/src/pupil_labs/neon_recording/stream/video_stream.py b/src/pupil_labs/neon_recording/stream/video_stream.py index 2760ebe..0ca8fdc 100644 --- a/src/pupil_labs/neon_recording/stream/video_stream.py +++ b/src/pupil_labs/neon_recording/stream/video_stream.py @@ -1,5 +1,7 @@ -from typing import Tuple, Optional import pathlib +from typing import Tuple, Optional +from enum import Enum + import numpy as np import pupil_labs.video as plv @@ -29,16 +31,28 @@ def _load_video(rec_dir: pathlib.Path, start_ts: float, video_name: str) -> Tupl return container, ts, ts_rel +class VideoType(Enum): + EYE = 1 + SCENE = 2 + + class VideoStream(Stream): + def __init__(self, name, type): + super().__init__(name) + self.type = type + + def interp_data(self, sorted_ts, method="nearest"): + log.warning("NeonRecording: Video streams only use nearest neighbor interpolation.") + if method != "nearest": raise ValueError("NeonRecording: Video streams only support nearest neighbor interpolation.") + elif method == "nearest_rob": + idxs = np.argmin(np.abs(self.ts - sorted_ts)) elif method == "nearest": - log.warning("NeonRecording: Video streams only use nearest neighbor interpolation.") - idxs = np.searchsorted(self.ts, sorted_ts) - return (self._data.frames[int(i)] for i in idxs) - # return self.data[idxs] + + return (self._data.frames[int(i)] for i in idxs) def load(self, rec_dir: pathlib.Path, start_ts: float, file_name: Optional[str] = None) -> None: diff --git a/todo.txt b/todo.txt index a0eec10..706f230 100644 --- a/todo.txt +++ b/todo.txt @@ -1,17 +1,17 @@ -- double-check timestamp alignment between gaze + eye + scene + imu +- other sampling ideas + = give me this stream from ts1 to ts2 sampled at freq of 60hz - get from cloud or run offline on file load? - fixation stream - eyestate/pupillometry stream -- interpolation methods when sampling streams - clarify how to deal with TsNs in imu stream - file to notify that it has been checked - for video, concatenate raw then convert to np array -specify default option for agg, and stream specific +specify default option for agg, and provide stream specific aggs see here: https://www.notion.so/pupillabs/Neon-Recording-Python-Lib-5b247c33e1c74f638af2964fa78018ff?pvs=4#6bf3848ff3ad435aa6ea8fe37e72ee9c