From 811403558db5b8b41bb0452546aa55dd7ca58380 Mon Sep 17 00:00:00 2001 From: Jon Myers Date: Thu, 6 Nov 2025 13:47:49 -0800 Subject: [PATCH 1/3] feat: add comprehensive spectrogram data access and visualization support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves Issue #46 Add complete spectrogram support to enable programmatic access to the same high-quality constant-Q spectrograms used in the IDTAP web application, with extensive matplotlib integration for computational musicology research. ## New Features: ### SpectrogramData Class (`idtap/spectrogram.py`) - **Data Loading**: - `from_audio_id(audio_id, client)` - Download from server - `from_piece(piece, client)` - Load from Piece object - Auto-decompresses gzipped spectrogram data from swara.studio - **Transformations**: - `apply_intensity(power)` - Power-law contrast enhancement (1.0-5.0) - `apply_colormap(data, cmap)` - 35+ matplotlib colormaps - `crop_frequency(min_hz, max_hz)` - Frequency range cropping - `crop_time(start_time, end_time)` - Time range cropping - **Matplotlib Integration** (for research workflows): - `plot_on_axis(ax, ...)` - Plot on existing axis for overlays - `get_plot_data(power, apply_cmap, cmap)` - Get processed data + extent - `get_extent()` - Get matplotlib extent [left, right, bottom, top] - **Image Generation**: - `to_image(width, height, power, cmap)` - Generate PIL Image - `to_matplotlib(figsize, power, cmap)` - Generate standalone figure - `save(filepath, ...)` - Save to file (PNG, JPG, etc.) - **Properties**: - `shape`, `duration`, `time_resolution`, `freq_bins` ### SwaraClient Updates (`idtap/client.py`) - `download_spectrogram_data(audio_id)` - Download compressed data - `download_spectrogram_metadata(audio_id)` - Download shape metadata ### Dependencies (`pyproject.toml`, `Pipfile`) - Added `numpy>=1.20.0` for array processing - Added `pillow>=9.0.0` for image generation - Added `matplotlib>=3.5.0` for visualization ## Testing: - 36 comprehensive test cases covering: - Data loading and initialization - Intensity transforms and colormap application - Frequency and time cropping - Matplotlib integration methods - Image generation and saving - All properties and edge cases - **All 401 tests pass** (365 existing + 36 new) ## Usage Example: ```python from idtap import SpectrogramData, get_piece import matplotlib.pyplot as plt # Load spectrogram piece = get_piece("transcription_id") spec = SpectrogramData.from_piece(piece) # Create visualization with spectrogram underlay fig, ax = plt.subplots(figsize=(12, 6)) spec.plot_on_axis(ax, power=2.5, cmap='viridis', alpha=0.6, zorder=0) # Overlay pitch contour ax.plot(times, freqs, 'r-', linewidth=2, zorder=1) ax.set_xlabel('Time (s)') ax.set_ylabel('Frequency (Hz)') plt.savefig('figure.png', dpi=300) ``` ## Documentation Updates: - Updated CLAUDE.md with testing warning about browser authorization ## Design Decisions: - Follows librosa/matplotlib patterns for research workflows - Optional client parameter (creates if not provided) - No caching in MVP (users can cache manually) - Uses matplotlib colormaps (close enough to D3, simpler) - Loads entire spectrogram into memory (suitable for typical sizes) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 5 + Pipfile | 5 +- Pipfile.lock | 1146 +++++++++++++++++++++++-------- idtap/__init__.py | 4 + idtap/client.py | 24 + idtap/spectrogram.py | 515 ++++++++++++++ idtap/tests/spectrogram_test.py | 449 ++++++++++++ pyproject.toml | 5 +- 8 files changed, 1880 insertions(+), 273 deletions(-) create mode 100644 idtap/spectrogram.py create mode 100644 idtap/tests/spectrogram_test.py diff --git a/CLAUDE.md b/CLAUDE.md index 762648e..ef980f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,11 @@ The Python API (`idtap`) is a sophisticated client library for interacting with - **Integration tests**: `python python/api_testing/api_test.py` (requires live server auth) - Test structure: Complete coverage of data models, client functionality, and authentication +**⚠️ IMPORTANT FOR CLAUDE: Before running the full test suite (`pytest idtap/tests/`), ALWAYS warn Jon first!** +- Some tests may require browser authorization for OAuth authentication +- Running tests without warning can waste time waiting for authorization that Jon doesn't realize is needed +- Best practice: Ask "Ready to run the full test suite? (May require browser authorization)" before executing + ### Build/Package/Publish - AUTOMATED via GitHub Actions **⚠️ IMPORTANT: Manual publishing is now automated. See "Automated Version Management" section below.** diff --git a/Pipfile b/Pipfile index 291ba86..530c4a9 100644 --- a/Pipfile +++ b/Pipfile @@ -14,7 +14,10 @@ build = "*" twine = "*" requests-toolbelt = "*" pyhumps = "*" -idtap = "*" +idtap = "==0.1.34" +numpy = "*" +pillow = "*" +matplotlib = "*" [dev-packages] responses = "*" diff --git a/Pipfile.lock b/Pipfile.lock index a44a0cd..aa8994b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e821823f367eb0d0aa393848da0b887b6a270c72152a839ea6ec7aa0383220dd" + "sha256": "337056d6dd7bc0129f01bf41df56b9c6827de0d149df485a5ebddbfadde6f9ed" }, "pipfile-spec": 6, "requires": { @@ -27,221 +27,375 @@ }, "cachetools": { "hashes": [ - "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", - "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a" + "sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701", + "sha256:3f391e4bd8f8bf0931169baf7456cc822705f4e2a31f840d218f445b9a854201" ], - "markers": "python_version >= '3.7'", - "version": "==5.5.2" + "markers": "python_version >= '3.9'", + "version": "==6.2.1" }, "certifi": { "hashes": [ - "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", - "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" + "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", + "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" ], "markers": "python_version >= '3.7'", - "version": "==2025.8.3" + "version": "==2025.10.5" }, "cffi": { "hashes": [ - "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", - "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", - "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", - "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", - "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", - "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", - "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", - "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", - "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", - "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", - "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", - "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", - "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", - "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", - "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", - "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", - "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", - "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", - "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", - "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", - "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", - "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", - "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", - "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", - "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", - "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", - "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", - "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", - "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", - "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", - "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", - "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", - "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", - "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", - "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", - "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", - "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", - "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", - "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", - "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", - "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", - "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", - "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", - "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", - "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", - "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", - "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", - "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", - "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", - "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", - "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", - "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", - "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", - "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", - "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", - "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", - "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", - "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", - "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", - "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", - "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", - "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", - "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", - "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", - "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", - "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", - "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" + "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", + "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", + "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", + "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", + "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", + "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", + "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", + "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", + "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", + "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", + "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", + "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", + "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", + "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", + "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", + "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", + "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", + "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", + "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", + "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", + "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", + "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", + "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", + "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", + "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", + "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", + "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", + "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", + "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", + "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", + "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", + "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", + "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", + "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", + "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", + "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", + "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", + "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", + "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", + "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", + "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", + "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", + "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", + "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", + "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", + "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", + "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", + "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", + "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", + "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", + "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", + "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", + "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", + "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", + "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", + "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", + "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", + "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", + "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", + "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", + "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", + "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", + "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", + "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", + "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", + "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", + "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", + "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", + "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", + "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", + "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", + "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", + "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", + "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", + "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", + "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", + "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", + "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", + "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", + "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", + "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", + "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", + "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", + "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" ], - "markers": "python_version >= '3.8'", - "version": "==1.17.1" + "markers": "python_version >= '3.9'", + "version": "==2.0.0" }, "charset-normalizer": { "hashes": [ - "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", - "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", - "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", - "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", - "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", - "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", - "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", - "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", - "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", - "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", - "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", - "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", - "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", - "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", - "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", - "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", - "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", - "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", - "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", - "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", - "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", - "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", - "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", - "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", - "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", - "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", - "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", - "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", - "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", - "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", - "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", - "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", - "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", - "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", - "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", - "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", - "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", - "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", - "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", - "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", - "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", - "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", - "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", - "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", - "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", - "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", - "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", - "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", - "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", - "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", - "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", - "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", - "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", - "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", - "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", - "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", - "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", - "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", - "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", - "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", - "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", - "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", - "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", - "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", - "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", - "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", - "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", - "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", - "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", - "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", - "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", - "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", - "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", - "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", - "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", - "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", - "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", - "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", - "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" ], "markers": "python_version >= '3.7'", - "version": "==3.4.3" + "version": "==3.4.4" + }, + "contourpy": { + "hashes": [ + "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", + "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", + "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", + "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", + "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", + "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", + "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", + "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", + "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", + "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", + "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", + "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", + "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", + "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", + "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", + "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", + "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", + "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", + "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", + "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", + "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", + "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", + "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", + "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", + "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", + "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", + "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", + "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", + "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", + "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", + "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", + "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", + "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", + "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", + "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", + "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", + "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", + "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", + "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", + "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", + "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", + "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", + "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", + "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", + "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", + "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", + "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", + "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", + "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", + "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", + "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", + "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", + "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", + "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", + "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", + "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", + "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", + "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", + "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", + "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", + "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", + "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", + "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", + "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", + "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", + "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", + "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", + "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", + "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", + "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", + "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", + "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a" + ], + "markers": "python_version >= '3.11'", + "version": "==1.3.3" }, "cryptography": { "hashes": [ - "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34", - "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513", - "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", - "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", - "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63", - "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130", - "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae", - "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", - "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", - "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", - "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf", - "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27", - "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde", - "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", - "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", - "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", - "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", - "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", - "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", - "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3", - "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083", - "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", - "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1", - "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", - "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", - "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", - "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7", - "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141", - "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3", - "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9", - "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4", - "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", - "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", - "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252", - "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", - "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b", - "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd" + "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", + "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", + "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", + "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", + "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", + "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", + "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", + "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", + "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", + "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", + "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", + "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", + "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", + "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", + "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", + "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", + "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", + "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", + "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", + "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", + "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", + "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", + "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", + "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", + "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", + "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", + "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", + "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", + "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", + "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", + "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", + "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", + "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", + "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", + "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", + "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", + "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", + "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", + "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", + "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", + "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", + "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", + "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", + "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", + "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", + "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", + "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", + "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", + "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", + "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", + "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", + "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", + "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", + "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018" ], "index": "pypi", - "markers": "python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'", - "version": "==45.0.7" + "markers": "python_version >= '3.8' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==46.0.3" + }, + "cycler": { + "hashes": [ + "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", + "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" }, "dnspython": { "hashes": [ @@ -259,22 +413,86 @@ "markers": "python_version >= '3.9'", "version": "==0.21.2" }, + "fonttools": { + "hashes": [ + "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", + "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", + "sha256:0b0835ed15dd5b40d726bb61c846a688f5b4ce2208ec68779bc81860adb5851a", + "sha256:0eae96373e4b7c9e45d099d7a523444e3554360927225c1cdae221a58a45b856", + "sha256:122e1a8ada290423c493491d002f622b1992b1ab0b488c68e31c413390dc7eb2", + "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", + "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", + "sha256:1525796c3ffe27bb6268ed2a1bb0dcf214d561dfaf04728abf01489eb5339dce", + "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", + "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", + "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", + "sha256:268ecda8ca6cb5c4f044b1fb9b3b376e8cd1b361cef275082429dc4174907038", + "sha256:282dafa55f9659e8999110bd8ed422ebe1c8aecd0dc396550b038e6c9a08b8ea", + "sha256:2ee06fc57512144d8b0445194c2da9f190f61ad51e230f14836286470c99f854", + "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", + "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", + "sha256:4ba4bd646e86de16160f0fb72e31c3b9b7d0721c3e5b26b9fa2fc931dfdb2652", + "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", + "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", + "sha256:596ecaca36367027d525b3b426d8a8208169d09edcf8c7506aceb3a38bfb55c7", + "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", + "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", + "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", + "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", + "sha256:7473a8ed9ed09aeaa191301244a5a9dbe46fe0bf54f9d6cd21d83044c3321217", + "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", + "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", + "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", + "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", + "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", + "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", + "sha256:8b4eb332f9501cb1cd3d4d099374a1e1306783ff95489a1026bde9eb02ccc34a", + "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", + "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", + "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", + "sha256:9a52f254ce051e196b8fe2af4634c2d2f02c981756c6464dc192f1b6050b4e28", + "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", + "sha256:a140761c4ff63d0cb9256ac752f230460ee225ccef4ad8f68affc723c88e2036", + "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", + "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", + "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", + "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", + "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", + "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", + "sha256:b42d86938e8dda1cd9a1a87a6d82f1818eaf933348429653559a458d027446da", + "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", + "sha256:c7420a2696a44650120cdd269a5d2e56a477e2bfa9d95e86229059beb1c19e15", + "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", + "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", + "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", + "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", + "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", + "sha256:ee0c0b3b35b34f782afc673d503167157094a16f442ace7c6c5e0ca80b08f50c", + "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", + "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", + "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", + "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", + "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa" + ], + "markers": "python_version >= '3.9'", + "version": "==4.60.1" + }, "google-auth": { "hashes": [ - "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", - "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77" + "sha256:754843be95575b9a19c604a848a41be03f7f2afd8c019f716dc1f51ee41c639d", + "sha256:b76b7b1f9e61f0cb7e88870d14f6a94aeef248959ef6992670efee37709cbfd2" ], "markers": "python_version >= '3.7'", - "version": "==2.40.3" + "version": "==2.41.1" }, "google-auth-oauthlib": { "hashes": [ - "sha256:11046fb8d3348b296302dd939ace8af0a724042e8029c1b872d87fabc9f41684", - "sha256:fd619506f4b3908b5df17b65f39ca8d66ea56986e5472eb5978fd8f3786f00a2" + "sha256:7c0940e037677f25e71999607493640d071212e7f3c15aa0febea4c47a5a0680", + "sha256:eb09e450d3cc789ecbc2b3529cb94a713673fd5f7a22c718ad91cf75aedc2ea4" ], "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==1.2.2" + "markers": "python_version >= '3.7'", + "version": "==1.2.3" }, "id": { "hashes": [ @@ -286,20 +504,20 @@ }, "idna": { "hashes": [ - "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", - "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" ], - "markers": "python_version >= '3.6'", - "version": "==3.10" + "markers": "python_version >= '3.8'", + "version": "==3.11" }, "idtap": { "hashes": [ - "sha256:3c8581b6a435e34ab3f17d95358b96c8c5b1b9c34355d3844c06e1f61fc940bb", - "sha256:5289b594d36ba90862c238618b9e92222ccd4fa5adecacfcbc6f27905af3d433" + "sha256:11d2a9df6811dc2f2f63e21c23e8c49dd6c8adcbefb73035e1dc618df95c80de", + "sha256:ef39df59ab4ab65d706be5192553b76993f665d5b41923ede0347ff5fb6b800e" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==0.1.14" + "version": "==0.1.34" }, "jaraco.classes": { "hashes": [ @@ -334,6 +552,113 @@ "markers": "python_version >= '3.9'", "version": "==25.6.0" }, + "kiwisolver": { + "hashes": [ + "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", + "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", + "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", + "sha256:0a590506f303f512dff6b7f75fd2fd18e16943efee932008fe7140e5fa91d80e", + "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", + "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", + "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", + "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", + "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", + "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", + "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", + "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", + "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", + "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", + "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", + "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", + "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", + "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", + "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", + "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", + "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", + "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", + "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", + "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", + "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", + "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", + "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", + "sha256:4d1d9e582ad4d63062d34077a9a1e9f3c34088a2ec5135b1f7190c07cf366527", + "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", + "sha256:540c7c72324d864406a009d72f5d6856f49693db95d1fbb46cf86febef873634", + "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", + "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", + "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", + "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", + "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", + "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", + "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", + "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", + "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", + "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", + "sha256:7b4da0d01ac866a57dd61ac258c5607b4cd677f63abaec7b148354d2b2cdd536", + "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", + "sha256:84fd60810829c27ae375114cd379da1fa65e6918e1da405f356a775d49a62bcf", + "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", + "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", + "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", + "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", + "sha256:8fdca1def57a2e88ef339de1737a1449d6dbf5fab184c54a1fca01d541317154", + "sha256:90f47e70293fc3688b71271100a1a5453aa9944a81d27ff779c108372cf5567b", + "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", + "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", + "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", + "sha256:9cf554f21be770f5111a1690d42313e140355e687e05cf82cb23d0a721a64a48", + "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", + "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", + "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", + "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", + "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", + "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", + "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", + "sha256:b4b4d74bda2b8ebf4da5bd42af11d02d04428b2c32846e4c2c93219df8a7987b", + "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", + "sha256:b78efa4c6e804ecdf727e580dbb9cba85624d2e1c6b5cb059c66290063bd99a9", + "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", + "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", + "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", + "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", + "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", + "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", + "sha256:c9e7cdf45d594ee04d5be1b24dd9d49f3d1590959b2271fb30b5ca2b262c00fb", + "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", + "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", + "sha256:ccd09f20ccdbbd341b21a67ab50a119b64a403b09288c27481575105283c1586", + "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", + "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", + "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", + "sha256:d4efec7bcf21671db6a3294ff301d2fc861c31faa3c8740d1a94689234d1b415", + "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", + "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", + "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", + "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", + "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", + "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", + "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", + "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", + "sha256:deed0c7258ceb4c44ad5ec7d9918f9f14fd05b2be86378d86cf50e63d1e7b771", + "sha256:e09c2279a4d01f099f52d5c4b3d9e208e91edcbd1a175c9662a8b16e000fece9", + "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", + "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", + "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", + "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", + "sha256:ede8c6d533bc6601a47ad4046080d36b8fc99f81e6f1c17b0ac3c2dc91ac7611", + "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", + "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", + "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", + "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", + "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", + "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", + "sha256:fb3b8132019ea572f4611d770991000d7f58127560c4889729248eb5852a102f", + "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", + "sha256:fc1795ac5cd0510207482c3d1d3ed781143383b8cfd36f5c645f3897ce066220" + ], + "markers": "python_version >= '3.10'", + "version": "==1.4.9" + }, "markdown-it-py": { "hashes": [ "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", @@ -342,6 +667,68 @@ "markers": "python_version >= '3.8'", "version": "==3.0.0" }, + "matplotlib": { + "hashes": [ + "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", + "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67", + "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", + "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", + "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", + "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f", + "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", + "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", + "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632", + "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2", + "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c", + "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", + "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", + "sha256:4a11c2e9e72e7de09b7b72e62f3df23317c888299c875e2b778abf1eda8c0a42", + "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", + "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", + "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", + "sha256:53cc80662dd197ece414dd5b66e07370201515a3eaf52e7c518c68c16814773b", + "sha256:5c09cf8f2793f81368f49f118b6f9f937456362bee282eac575cca7f84cda537", + "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b", + "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", + "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e", + "sha256:667ecd5d8d37813a845053d8f5bf110b534c3c9f30e69ebd25d4701385935a6d", + "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc", + "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", + "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", + "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815", + "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67", + "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", + "sha256:7ac81eee3b7c266dd92cee1cd658407b16c57eed08c7421fa354ed68234de380", + "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", + "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", + "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355", + "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", + "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf", + "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9", + "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1", + "sha256:b498e9e4022f93de2d5a37615200ca01297ceebbb56fe4c833f46862a490f9e3", + "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", + "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84", + "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", + "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", + "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", + "sha256:cc1c51b846aca49a5a8b44fbba6a92d583a35c64590ad9e1e950dc88940a4297", + "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84", + "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", + "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", + "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", + "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", + "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", + "sha256:de66744b2bb88d5cd27e80dfc2ec9f0517d0a46d204ff98fe9e5f2864eb67657", + "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1", + "sha256:f19410b486fdd139885ace124e57f938c1e6a3210ea13dd29cab58f5d4bc12c7", + "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", + "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==3.10.7" + }, "mdurl": { "hashes": [ "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", @@ -390,6 +777,87 @@ "markers": "python_version >= '3.8'", "version": "==0.3.0" }, + "numpy": { + "hashes": [ + "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", + "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", + "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", + "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", + "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d", + "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", + "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", + "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36", + "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", + "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", + "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", + "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7", + "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", + "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", + "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", + "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", + "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c", + "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", + "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", + "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", + "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", + "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a", + "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", + "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e", + "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", + "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05", + "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", + "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", + "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f", + "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", + "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", + "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", + "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", + "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", + "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", + "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", + "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", + "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", + "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", + "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", + "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", + "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", + "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847", + "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", + "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", + "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0", + "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", + "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", + "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", + "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", + "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", + "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda", + "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", + "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", + "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032", + "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", + "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", + "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", + "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346", + "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", + "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", + "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", + "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", + "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", + "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb", + "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", + "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", + "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1", + "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", + "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", + "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996", + "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", + "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b", + "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb" + ], + "index": "pypi", + "markers": "python_version >= '3.11'", + "version": "==2.3.4" + }, "oauthlib": { "hashes": [ "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", @@ -406,6 +874,104 @@ "markers": "python_version >= '3.8'", "version": "==25.0" }, + "pillow": { + "hashes": [ + "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", + "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", + "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", + "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", + "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", + "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", + "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1", + "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", + "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", + "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", + "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", + "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", + "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", + "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", + "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", + "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10", + "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", + "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", + "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b", + "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", + "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", + "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d", + "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", + "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", + "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", + "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", + "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", + "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", + "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", + "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", + "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", + "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7", + "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", + "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", + "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", + "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", + "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", + "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", + "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", + "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", + "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", + "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", + "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", + "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", + "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", + "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", + "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", + "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", + "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", + "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", + "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", + "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", + "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", + "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", + "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", + "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", + "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", + "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", + "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", + "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", + "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", + "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", + "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", + "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", + "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275", + "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", + "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", + "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", + "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", + "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", + "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", + "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca", + "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa", + "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", + "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", + "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", + "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", + "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", + "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", + "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363", + "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", + "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e", + "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782", + "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", + "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", + "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", + "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", + "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", + "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", + "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", + "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==12.0.0" + }, "pyasn1": { "hashes": [ "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", @@ -424,11 +990,11 @@ }, "pycparser": { "hashes": [ - "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", - "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", + "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934" ], "markers": "python_version >= '3.8'", - "version": "==2.22" + "version": "==2.23" }, "pygments": { "hashes": [ @@ -457,67 +1023,89 @@ }, "pymongo": { "hashes": [ - "sha256:019f8f9b8a61a5780450c5908c38f63e4248f286d804163d3728bc544f0b07b2", - "sha256:04e780ff2854278d24f7a2011aed45b3df89520c89ca29a7c1ccf9a9f0d513d0", - "sha256:06e2e8996324823e19bccea4dfd7ed543513410bbc7be9860502b62822d62bd4", - "sha256:0e679c8f62ec0e6ba64799ce55b22d76c80cd042f7d99fa2cfbb4d935ac61bea", - "sha256:10a37312c841be2c2edd090b49861dab2e6117ff15cabf801f5910931105740e", - "sha256:147711a3b95d45dd11377a078e77fa302142b67656a8f57076693aa7fba124c1", - "sha256:1604d9f669b044d30ca1775ebe37ddbd1972eaa7ffd041dde9e026b0334c69bd", - "sha256:184b0b6c3663bec2c13d7e2f0a99233c24b1bc7d8163b8b9a019a3ab159b1ade", - "sha256:1f5a4223c6acecb0ab25202a5b4ed6f2b6a41c30204ef44d3d46525e8ea455a9", - "sha256:27cb44c71e6f220b163e1d3c0dd18559e534d5d7cb7e16afa0cf1b7761403492", - "sha256:3176250b89ecc0db8120caf9945ded340eacebec7183f2093e58370041c2d5a8", - "sha256:33a8b2c47db66f3bb33d62e3884fb531b77a58efd412b67b0539c685950c2382", - "sha256:414a999a5b9212635f51c8b23481626406b731abaea16659a39df00f538d06d8", - "sha256:44beff3470a6b1736f9e9cf7fb6477fdb2342b6f19a722cab3bbc989c5f3f693", - "sha256:4812d168f9cd5f257805807a44637afcd0bb7fd22ac4738321bc6aa50ebd9d4f", - "sha256:49a2bf594ce1693f8a3cc4123ec3fa3a86215b395333b22be83c9eb765b24ecb", - "sha256:4c93d1f5db2bf63b4958aef2a914520c7103187d68359b512a8d6d62f5d7a752", - "sha256:573b1ed740dbb51be0819ede005012f4fa37df2c27c94d7d2e18288e16e1ef10", - "sha256:5c95ce2e0dcd9a556e1f51a4132db88c40e8e0a49c0b16d1dddba624f640895b", - "sha256:5f08880ad8bd6bdd4bdb5c93c4a6946c5c4e429b648c3b665c435af02005e7db", - "sha256:6649018ae12a28b8d8399ddda5cb662ac364e338faf0a621e6b9e5ec643134df", - "sha256:6b945dda0359ba13171201fa2f1e32d4b5e73f57606b8c6dd560eeebf4a69d84", - "sha256:6eeea7c92fd8ccd24ad156e2f9c2a117220f1ba0a41968b26d953dc6b8082b1d", - "sha256:714589ce1df891e91f808b1e6e678990040997972d2c70454efebfefd1c8e299", - "sha256:71500e97dbbda5d3e5dc9354dca865246c7502eea9d041c1ce0ae2c3fa018fd2", - "sha256:78e9ec6345a14e2144a514f501e3bfe69ec8c8fefd0759757e4f47bf0b243522", - "sha256:89c1f6804ae16101d5dd6cf0bd06b10e70e5e870aa98a198824c772ce3cb8ba3", - "sha256:8a4fe1b1603865e44c3dbce2b91ac2f18b1672208ff49203e8a480ab68a2d8f5", - "sha256:91f9a3d771ab86229244098125b1c22111aa3e3679534d626db8d05cd9c59ea4", - "sha256:92f8c2a3d0f17c432d68304d3abcab36a8a7ba78db93a143ac77eef6b70bc126", - "sha256:9375cf27c04d2be7d02986262e0593ece1e78fa1934744bdd74c0c0b0cd2c2f2", - "sha256:9485278fed0a8933c8ce8f97ab518158b82e884d4a7bc34e1d784b751c7b69f3", - "sha256:95bfb5fe10a8aa11029868c403939945092fb8d160ca3a10d386778ed9623533", - "sha256:97f0da391fb32f989f0afcd1838faff5595456d24c56d196174eddbb7c3a494c", - "sha256:98c36403c97ec3a439a9ea5cdea730e34f0bf3c39eacfcab3fb07b34f5ef42a7", - "sha256:9fba1dcad4260a9c96aa5bd576bf96edeea5682cd6da6b5777c644ef103f16f6", - "sha256:a76afb1375f6914fecfdc3bfe6fb7c8c36b682c4707b7fb8ded5c2e17a1c2d77", - "sha256:af4e667902314bcc05c90ea4ac0351bb759410ae0c5496ae47aef80659a12a44", - "sha256:b7d6114f4a60b04205b4fce120567955402816ac75329b9282fc8a603ac615ef", - "sha256:c6d426e70a35d1dd5003a535ac8c0683998bea783949daa980d70272baa5cb05", - "sha256:cb147d0d77863ae89fa73cf8c0cc1a68d7dd7c5689cf0381501505307136b2bd", - "sha256:d2cafb545a77738f0506cd538be1b14e9f40ad0b62634d89e1845dee3c726ad5", - "sha256:d78f5b0b569f4320e2485599d89b088aa6d750aad17cc98fd81a323b544ed3d0", - "sha256:d8945b11c4e39c13b47ec79dd0ee05126a6cf4753cf5fdceabf8cc51c02e21e6", - "sha256:df5cc411dbe2b064945114598fdb3e36c3eeb38ed2559e459d5a7b2d91074a54", - "sha256:e09e59bb15edf0d948de6fa2b6f1cbb25ee63e7beba6d45ef6e94609e759efaa", - "sha256:e0a9bdb95e6fab64c8453dae84834dfd7a8b91cfbc7a3e288d9cdd161621a867", - "sha256:e0bd1a446b39216453f53d55143a82e8617730723f100de940f1611ee35e78d6", - "sha256:e0fe8e7bbb59cb0652df0efd285e80e6a92207f5ced4a0f7de56275fd9c21b77", - "sha256:e386721b57a50a5acd6e19c3c14cb975cbc0bf1a0364227d6cc15b486bb094cc", - "sha256:eaef22550ba1034e9b0ed309395ec72944348c277e27cc973cd5b07322b1d088", - "sha256:ebb6679929e5bab898e9c5b46ee6fd025f6eb14380e9d4a210e122d79b223548", - "sha256:ec160c4e1184da11d375a4315917f5a04180ea0ff522f0a97cf78acbb65810d8", - "sha256:ed9c0e22f874419f07022a9133e8d62aa8b665ceb2d89218ee88450c2824185e", - "sha256:f7b965614c16ac7d2cf297fbfb16a9ec81c0493bd5916f455a8e8020e432300b", - "sha256:f81e8156a862ad8b44a065bd89978361a3054571e61b5e802ebdef91bb13ccad", - "sha256:fcbea95a877b2c7c4e4a18527c4eecbe91bdcb0b202f93d5713d50386138ffa3" + "sha256:07bcc36d11252f24fe671e7e64044d39a13d997b0502c6401161f28cc144f584", + "sha256:09440e78dff397b2f34a624f445ac8eb44c9756a2688b85b3bf344d351d198e1", + "sha256:1246a82fa6dd73ac2c63aa7e463752d5d1ca91e0c7a23396b78f21273befd3a7", + "sha256:17d13458baf4a6a9f2e787d95adf8ec50d412accb9926a044bd1c41029c323b2", + "sha256:17fc94d1e067556b122eeb09e25c003268e8c0ea1f2f78e745b33bb59a1209c4", + "sha256:1f681722c9f27e86c49c2e8a838e61b6ecf2285945fd1798bd01458134257834", + "sha256:21c0a95a4db72562fd0805e2f76496bf432ba2e27a5651f4b9c670466260c258", + "sha256:292fd5a3f045751a823a54cdea75809b2216a62cc5f74a1a96b337db613d46a8", + "sha256:2c96dde79bdccd167b930a709875b0cd4321ac32641a490aebfa10bdcd0aa99b", + "sha256:2f3d66f7c495efc3cfffa611b36075efe86da1860a7df75522a6fe499ee10383", + "sha256:2fd3b99520f2bb013960ac29dece1b43f2f1b6d94351ca33ba1b1211ecf79a09", + "sha256:300eaf83ad053e51966be1839324341b08eaf880d3dc63ada7942d5912e09c49", + "sha256:3561fa96c3123275ec5ccf919e595547e100c412ec0894e954aa0da93ecfdb9e", + "sha256:390c4954c774eda280898e73aea36482bf20cba3ecb958dbb86d6a68b9ecdd68", + "sha256:39a13d8f7141294404ce46dfbabb2f2d17e9b1192456651ae831fa351f86fbeb", + "sha256:446417a34ff6c2411ce3809e17ce9a67269c9f1cb4966b01e49e0c590cc3c6b3", + "sha256:45aebbd369ca79b7c46eaea5b04d2e4afca4eda117b68965a07a9da05d774e4d", + "sha256:47ffb068e16ae5e43580d5c4e3b9437f05414ea80c32a1e5cac44a835859c259", + "sha256:482ca9b775747562ce1589df10c97a0e62a604ce5addf933e5819dd967c5e23c", + "sha256:49fd6e158cf75771b2685a8a221a40ab96010ae34dd116abd06371dc6c38ab60", + "sha256:4a0a054e9937ec8fdb465835509b176f6b032851c8648f6a5d1b19932d0eacd6", + "sha256:52f40c4b8c00bc53d4e357fe0de13d031c4cddb5d201e1a027db437e8d2887f8", + "sha256:58d0f4123855f05c0649f9b8ee083acc5b26e7f4afde137cd7b8dc03e9107ff3", + "sha256:5bf879a6ed70264574d4d8fb5a467c2a64dc76ecd72c0cb467c4464f849c8c77", + "sha256:5c78237e878e0296130e398151b0d4aa6c9eaf82e38fb6e0aaae2029bc7ef0ce", + "sha256:5c85a4c72b7965033f95c94c42dac27d886c01dbc23fe337ccb14f052a0ccc29", + "sha256:5f6feb678f26171f2a6b2cbb340949889154c7067972bd4cc129b62161474f08", + "sha256:6a054d282dd922ac400b6f47ea3ef58d8b940968d76d855da831dc739b7a04de", + "sha256:71413cd8f091ae25b1fec3af7c2e531cf9bdb88ce4079470e64835f6a664282a", + "sha256:76a8d4de8dceb69f6e06736198ff6f7e1149515ef946f192ff2594d2cc98fc53", + "sha256:77353978be9fc9e5fe56369682efed0aac5f92a2a1570704d62b62a3c9e1a24f", + "sha256:7a981271347623b5319932796690c2d301668ac3a1965974ac9f5c3b8a22cea5", + "sha256:7c0fd3de3a12ff0a8113a3f64cedb01f87397ab8eaaffa88d7f18ca66cd39385", + "sha256:7dd2a49f088890ca08930bbf96121443b48e26b02b84ba0a3e1ae2bf2c5a9b48", + "sha256:82a490f1ade4ec6a72068e3676b04c126e3043e69b38ec474a87c6444cf79098", + "sha256:86b1b5b63f4355adffc329733733a9b71fdad88f37a9dc41e163aed2130f9abc", + "sha256:89e45d7fa987f4e246cdf43ff001e3f911f73eb19ba9dabc2a6d80df5c97883b", + "sha256:8bd6dd736f5d07a825caf52c38916d5452edc0fac7aee43ec67aba6f61c2dbb7", + "sha256:8d4b01a48369ea6d5bc83fea535f56279f806aa3e4991189f0477696dd736289", + "sha256:90ad56bd1d769d2f44af74f0fd0c276512361644a3c636350447994412cbc9a1", + "sha256:9483521c03f6017336f54445652ead3145154e8d3ea06418e52cea57fee43292", + "sha256:959ef69c5e687b6b749fbf2140c7062abdb4804df013ae0507caabf30cba6875", + "sha256:97f9babdb98c31676f97d468f7fe2dc49b8a66fb6900effddc4904c1450196c8", + "sha256:982107c667921e896292f4be09c057e2f1a40c645c9bfc724af5dd5fb8398094", + "sha256:9897a837677e3814873d0572f7e5d53c23ce18e274f3b5b87f05fb6eea22615b", + "sha256:9b03db2fe37c950aff94b29ded5c349b23729bccd90a0a5907bbf807d8c77298", + "sha256:9bc9f99e7702fdb0dcc3ff1dd490adc5d20b3941ad41e58f887d4998b9922a14", + "sha256:9df2db6bd91b07400879b6ec89827004c0c2b55fc606bb62db93cafb7677c340", + "sha256:a47a3218f7900f65bf0f36fcd1f2485af4945757360e7e143525db9d715d2010", + "sha256:b33d59bf6fa1ca1d7d96d4fccff51e41312358194190d53ef70a84c070f5287e", + "sha256:b3a0ec660d61efb91c16a5962ec937011fe3572c4338216831f102e53d294e5c", + "sha256:b63bac343b79bd209e830aac1f5d9d552ff415f23a924d3e51abbe3041265436", + "sha256:bd0497c564b0ae34fb816464ffc09986dd9ca29e2772a0f7af989e472fecc2ad", + "sha256:c4fdd8e6eab8ff77c1c8041792b5f760d48508623cd10b50d5639e73f1eec049", + "sha256:c57dad9f289d72af1d7c47a444c4d9fa401f951cedbbcc54c7dd0c2107d6d786", + "sha256:c7eb497519f42ac89c30919a51f80e68a070cfc2f3b0543cac74833cd45a6b9c", + "sha256:cfa4a0a0f024a0336640e1201994e780a17bda5e6a7c0b4d23841eb9152e868b", + "sha256:d09d895c7f08bcbed4d2e96a00e52e9e545ae5a37b32d2dc10099b205a21fc6d", + "sha256:d2d4ca446348d850ac4a5c3dc603485640ae2e7805dbb90765c3ba7d79129b37", + "sha256:d66da207ccb0d68c5792eaaac984a0d9c6c8ec609c6bcfa11193a35200dc5992", + "sha256:dc583a1130e2516440b93bb2ecb55cfdac6d5373615ae472a9d1f26801f58749", + "sha256:dcff15b9157c16bc796765d4d3d151df669322acfb0357e4c3ccd056153f0ff4", + "sha256:de3bc878c3be54ae41c2cabc9e9407549ed4fec41f4e279c04e840dddd7c630c", + "sha256:e7cde58ef6470c0da922b65e885fb1ffe04deef81e526bd5dea429290fa358ca", + "sha256:e84dec392cf5f72d365e0aac73f627b0a3170193ebb038c3f7e7df11b7983ee7", + "sha256:f6b0513e5765fdde39f36e6a29a36c67071122b5efa748940ae51075beb5e4bc", + "sha256:fae552767d8e5153ed498f1bca92d905d0d46311d831eefb0f06de38f7695c95", + "sha256:fb384623ece34db78d445dd578a52d28b74e8319f4d9535fbaff79d0eae82b3d", + "sha256:fe4bcb8acfb288e238190397d4a699aeb4adb70e8545a6f4e44f99d4e8096ab1", + "sha256:ff99864085d2c7f4bb672c7167680ceb7d273e9a93c1a8074c986a36dbb71cc6", + "sha256:ffe217d2502f3fba4e2b0dc015ce3b34f157b66dfe96835aa64432e909dd0d95" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==4.14.1" + "version": "==4.15.3" + }, + "pyparsing": { + "hashes": [ + "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", + "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e" + ], + "markers": "python_version >= '3.9'", + "version": "==3.2.5" }, "pyproject-hooks": { "hashes": [ @@ -527,6 +1115,14 @@ "markers": "python_version >= '3.7'", "version": "==1.2.0" }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, "readme-renderer": { "hashes": [ "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", @@ -585,6 +1181,14 @@ "markers": "python_version >= '3.6' and python_version < '4'", "version": "==4.9.1" }, + "six": { + "hashes": [ + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" + }, "twine": { "hashes": [ "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384", diff --git a/idtap/__init__.py b/idtap/__init__.py index b0540d4..4ac6ebb 100644 --- a/idtap/__init__.py +++ b/idtap/__init__.py @@ -21,6 +21,7 @@ from .classes.trajectory import Trajectory from .enums import Instrument +from .spectrogram import SpectrogramData, SUPPORTED_COLORMAPS from .audio_models import ( AudioMetadata, AudioUploadResult, @@ -74,6 +75,9 @@ "Trajectory", "Instrument", "login_google", + # Spectrogram + "SpectrogramData", + "SUPPORTED_COLORMAPS", # Audio upload classes "AudioMetadata", "AudioUploadResult", diff --git a/idtap/client.py b/idtap/client.py index ef9684a..c096f14 100644 --- a/idtap/client.py +++ b/idtap/client.py @@ -684,6 +684,30 @@ def download_and_save_transcription_audio(self, piece: Union[Dict[str, Any], Pie # Save file and return path return self.save_audio_file(audio_data, filename, filepath) + def download_spectrogram_data(self, audio_id: str) -> bytes: + """Download gzip-compressed spectrogram data. + + Args: + audio_id: The audio recording ID + + Returns: + Gzipped binary data containing uint8 spectrogram array + """ + endpoint = f"spec_data/{audio_id}/spec_data.gz" + return self._get(endpoint) + + def download_spectrogram_metadata(self, audio_id: str) -> Dict[str, Any]: + """Download spectrogram shape metadata. + + Args: + audio_id: The audio recording ID + + Returns: + Dictionary with 'shape' key: [freq_bins, time_frames] + """ + endpoint = f"spec_data/{audio_id}/spec_shape.json" + return self._get(endpoint) + def save_transcription(self, piece: Piece, fill_duration: bool = True) -> Any: """Save a transcription piece to the server. diff --git a/idtap/spectrogram.py b/idtap/spectrogram.py new file mode 100644 index 0000000..d9c52af --- /dev/null +++ b/idtap/spectrogram.py @@ -0,0 +1,515 @@ +"""Spectrogram data access and visualization for IDTAP audio recordings.""" + +from __future__ import annotations + +import gzip +import numpy as np +from typing import Optional, Tuple, Dict, Any, List, TYPE_CHECKING +from pathlib import Path + +if TYPE_CHECKING: + from .client import SwaraClient + from .classes.piece import Piece + from PIL import Image + from matplotlib.figure import Figure + from matplotlib.axes import Axes + from matplotlib.image import AxesImage + + +# Supported matplotlib colormaps (matches web app functionality) +SUPPORTED_COLORMAPS = [ + # Perceptually uniform + 'viridis', 'plasma', 'inferno', 'magma', 'cividis', 'turbo', + # Sequential + 'Blues', 'Greens', 'Reds', 'Oranges', 'Purples', 'Greys', + 'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu', + 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd', + # Diverging + 'RdBu', 'BrBG', 'PRGn', 'PiYG', 'PuOr', 'RdGy', 'RdYlBu', 'RdYlGn', 'Spectral', + # Cyclical + 'rainbow', 'hsv', + # Temperature + 'cool', 'warm', 'coolwarm' +] + + +class SpectrogramData: + """Constant-Q spectrogram data for IDTAP audio recordings. + + This class provides access to the same high-quality constant-Q transform + spectrograms used in the IDTAP web application, with tools for visualization, + manipulation, and integration with matplotlib-based research workflows. + + Attributes: + audio_id: IDTAP audio recording ID + freq_range: Tuple of (min_hz, max_hz) for the frequency range + bins_per_octave: Number of frequency bins per octave + """ + + # Constants matching web app implementation + DEFAULT_FREQ_RANGE = (75.0, 2400.0) # Hz + DEFAULT_BINS_PER_OCTAVE = 72 + + def __init__(self, data: np.ndarray, audio_id: str, + freq_range: Tuple[float, float] = DEFAULT_FREQ_RANGE, + bins_per_octave: int = DEFAULT_BINS_PER_OCTAVE): + """Initialize SpectrogramData with raw data. + + Args: + data: Raw uint8 spectrogram array [freq_bins, time_frames] + audio_id: Audio recording ID + freq_range: Frequency range (min_hz, max_hz) + bins_per_octave: Number of frequency bins per octave + """ + if not isinstance(data, np.ndarray): + raise TypeError(f"data must be numpy array, got {type(data)}") + if data.dtype != np.uint8: + raise TypeError(f"data must be uint8 array, got {data.dtype}") + if data.ndim != 2: + raise ValueError(f"data must be 2D array, got {data.ndim}D") + + self._data = data + self.audio_id = audio_id + self.freq_range = freq_range + self.bins_per_octave = bins_per_octave + + @classmethod + def from_audio_id(cls, audio_id: str, client: Optional['SwaraClient'] = None) -> 'SpectrogramData': + """Download and load spectrogram data from audio ID. + + Fetches compressed spectrogram data from https://swara.studio/spec_data/{audio_id}/ + + Args: + audio_id: IDTAP audio recording ID + client: Optional SwaraClient instance (creates one if not provided) + + Returns: + SpectrogramData instance + + Raises: + requests.HTTPError: If spectrogram data doesn't exist or download fails + """ + # Create client if not provided + if client is None: + from .client import SwaraClient + client = SwaraClient() + + # Download compressed data and metadata + compressed_data = client.download_spectrogram_data(audio_id) + metadata = client.download_spectrogram_metadata(audio_id) + + # Decompress data + decompressed = gzip.decompress(compressed_data) + + # Reshape to numpy array + shape = tuple(metadata['shape']) # [freq_bins, time_frames] + data = np.frombuffer(decompressed, dtype=np.uint8).reshape(shape) + + return cls(data, audio_id) + + @classmethod + def from_piece(cls, piece: 'Piece', client: Optional['SwaraClient'] = None) -> Optional['SpectrogramData']: + """Load spectrogram data from a Piece object. + + Args: + piece: Piece object with audio_id attribute + client: Optional SwaraClient instance + + Returns: + SpectrogramData instance, or None if piece has no audio_id + """ + if not hasattr(piece, 'audio_id') or piece.audio_id is None: + return None + return cls.from_audio_id(piece.audio_id, client) + + def apply_intensity(self, power: float = 1.0) -> np.ndarray: + """Apply power-law intensity transformation (matches web app behavior). + + This transformation enhances visual contrast in the spectrogram. + Formula: output = (input^power / 255^power) * 255 + + Args: + power: Exponent for power transform (1.0-5.0) + 1.0 = linear (no change) + >1.0 = increased contrast + + Returns: + Transformed uint8 array with same shape as input + + Raises: + ValueError: If power is outside valid range [1.0, 5.0] + """ + if not 1.0 <= power <= 5.0: + raise ValueError(f"Power must be between 1.0 and 5.0, got {power}") + + if power == 1.0: + return self._data.copy() + + # Vectorized power transform + # Convert to float for precision, apply transform, convert back + data_float = self._data.astype(np.float32) + transformed = np.power(data_float / 255.0, power) * 255.0 + return np.clip(transformed, 0, 255).astype(np.uint8) + + def apply_colormap(self, data: Optional[np.ndarray] = None, + cmap: str = 'viridis') -> np.ndarray: + """Apply matplotlib colormap to spectrogram data. + + Args: + data: Input spectrogram data (if None, uses self._data) + cmap: Matplotlib colormap name (see SUPPORTED_COLORMAPS) + + Returns: + RGB array of shape [height, width, 3] with uint8 values + + Raises: + ValueError: If colormap name is not recognized + """ + import matplotlib.pyplot as plt + + if data is None: + data = self._data + + # Get matplotlib colormap + try: + colormap = plt.get_cmap(cmap) + except ValueError: + raise ValueError( + f"Unknown colormap: '{cmap}'. " + f"See SUPPORTED_COLORMAPS for valid options." + ) + + # Apply colormap (handles normalization automatically) + # Returns RGBA array, we take only RGB channels + colored = colormap(data / 255.0) # Normalize to [0, 1] + rgb = (colored[:, :, :3] * 255).astype(np.uint8) + + return rgb + + def crop_frequency(self, min_hz: Optional[float] = None, + max_hz: Optional[float] = None) -> 'SpectrogramData': + """Crop spectrogram to a specific frequency range. + + Args: + min_hz: Minimum frequency (Hz), defaults to original min + max_hz: Maximum frequency (Hz), defaults to original max + + Returns: + New SpectrogramData instance with cropped data + """ + if min_hz is None: + min_hz = self.freq_range[0] + if max_hz is None: + max_hz = self.freq_range[1] + + # Calculate bin indices for frequency range + freq_bins = self.freq_bins + + # Find closest bin indices + min_idx = np.searchsorted(freq_bins, min_hz) + max_idx = np.searchsorted(freq_bins, max_hz) + + # Ensure valid range + min_idx = max(0, min_idx) + max_idx = min(len(freq_bins), max_idx) + + # Crop data + cropped_data = self._data[min_idx:max_idx, :] + + # Create new instance with updated frequency range + return SpectrogramData( + cropped_data, + self.audio_id, + freq_range=(freq_bins[min_idx], freq_bins[max_idx - 1] if max_idx > min_idx else freq_bins[min_idx]), + bins_per_octave=self.bins_per_octave + ) + + def crop_time(self, start_time: Optional[float] = None, + end_time: Optional[float] = None) -> 'SpectrogramData': + """Crop spectrogram to a specific time range. + + Args: + start_time: Start time in seconds (defaults to 0) + end_time: End time in seconds (defaults to duration) + + Returns: + New SpectrogramData instance with cropped data + """ + if start_time is None: + start_time = 0.0 + if end_time is None: + end_time = self.duration + + # Convert times to frame indices + start_frame = int(start_time / self.time_resolution) + end_frame = int(end_time / self.time_resolution) + + # Ensure valid range + start_frame = max(0, start_frame) + end_frame = min(self.shape[1], end_frame) + + # Crop data + cropped_data = self._data[:, start_frame:end_frame] + + return SpectrogramData( + cropped_data, + self.audio_id, + freq_range=self.freq_range, + bins_per_octave=self.bins_per_octave + ) + + def get_extent(self) -> List[float]: + """Get matplotlib extent for this spectrogram. + + Returns: + [left, right, bottom, top] = [0, duration, min_freq, max_freq] + This is the format matplotlib imshow() expects for extent parameter. + """ + return [0, self.duration, self.freq_range[0], self.freq_range[1]] + + def get_plot_data(self, power: float = 1.0, + apply_cmap: bool = False, + cmap: str = 'viridis') -> Tuple[np.ndarray, List[float]]: + """Get processed spectrogram data and extent for matplotlib plotting. + + Use this when you need direct control over the plotting process, + or when you want to manipulate the data before plotting. + + Args: + power: Intensity power transform (1.0-5.0) + apply_cmap: If True, returns RGB array; if False, returns grayscale uint8 + cmap: Colormap name (only used if apply_cmap=True) + + Returns: + Tuple of (data, extent): + - data: Processed spectrogram array + If apply_cmap=False: uint8 array [freq_bins, time_frames] + If apply_cmap=True: RGB uint8 array [freq_bins, time_frames, 3] + - extent: [left, right, bottom, top] for matplotlib imshow() + + Example: + >>> # Low-level control + >>> data, extent = spec.get_plot_data(power=2.5) + >>> fig, ax = plt.subplots() + >>> im = ax.imshow(data, extent=extent, aspect='auto', + ... origin='lower', cmap='magma') + """ + # Apply intensity transform + transformed = self.apply_intensity(power) + + # Optionally apply colormap + if apply_cmap: + data = self.apply_colormap(transformed, cmap) + else: + data = transformed + + # Get extent + extent = self.get_extent() + + return data, extent + + def plot_on_axis(self, ax: 'Axes', + power: float = 1.0, + cmap: str = 'viridis', + alpha: float = 1.0, + zorder: int = 0, + **imshow_kwargs) -> 'AxesImage': + """Plot spectrogram on an existing matplotlib axis (for overlays). + + This is the primary method for using spectrograms as underlays in + custom matplotlib visualizations. + + Args: + ax: Matplotlib axis to plot on + power: Intensity power transform (1.0-5.0) + cmap: Matplotlib colormap name + alpha: Transparency (0.0-1.0), useful for subtle underlays + zorder: Drawing order (0 = background, higher = foreground) + **imshow_kwargs: Additional arguments passed to ax.imshow() + + Returns: + AxesImage object (useful for adding colorbars) + + Example: + >>> fig, ax = plt.subplots(figsize=(12, 6)) + >>> im = spec.plot_on_axis(ax, power=2.0, cmap='viridis', alpha=0.7) + >>> ax.plot(times, pitch_contour, 'r-', linewidth=2) # Overlay pitch + >>> ax.set_xlabel('Time (s)') + >>> ax.set_ylabel('Frequency (Hz)') + >>> plt.colorbar(im, ax=ax, label='Intensity') + """ + # Get processed data and extent + data, extent = self.get_plot_data(power=power, apply_cmap=False) + + # Plot on provided axis + im = ax.imshow( + data, + extent=extent, + aspect='auto', + origin='lower', + cmap=cmap, + alpha=alpha, + zorder=zorder, + **imshow_kwargs + ) + + return im + + def to_image(self, width: Optional[int] = None, + height: Optional[int] = None, + power: float = 1.0, + cmap: str = 'viridis', + interpolation: str = 'bilinear') -> 'Image': + """Generate PIL Image with full processing pipeline. + + Args: + width: Output width in pixels (default: original width) + height: Output height in pixels (default: original height) + power: Intensity power transform (1.0-5.0) + cmap: Matplotlib colormap name + interpolation: Resampling method ('bilinear', 'nearest', 'lanczos', etc.) + See PIL.Image.Resampling for all options + + Returns: + PIL Image in RGB mode + """ + from PIL import Image + + # Apply intensity transform + transformed = self.apply_intensity(power) + + # Apply colormap + rgb = self.apply_colormap(transformed, cmap) + + # Create PIL Image + img = Image.fromarray(rgb, mode='RGB') + + # Resize if requested + if width or height: + # Determine final size + orig_height, orig_width = rgb.shape[:2] + + if width and height: + new_size = (width, height) + elif width: + # Keep aspect ratio + ratio = width / orig_width + new_size = (width, int(orig_height * ratio)) + else: # height only + ratio = height / orig_height + new_size = (int(orig_width * ratio), height) + + # Map interpolation string to PIL constant + from PIL import Image as PILImage + resample_map = { + 'nearest': PILImage.Resampling.NEAREST, + 'bilinear': PILImage.Resampling.BILINEAR, + 'bicubic': PILImage.Resampling.BICUBIC, + 'lanczos': PILImage.Resampling.LANCZOS, + } + resample = resample_map.get(interpolation.lower(), PILImage.Resampling.BILINEAR) + + img = img.resize(new_size, resample=resample) + + return img + + def to_matplotlib(self, figsize: Tuple[float, float] = (12, 6), + power: float = 1.0, + cmap: str = 'viridis', + show_colorbar: bool = True, + show_axes: bool = True) -> 'Figure': + """Generate standalone matplotlib Figure for publication. + + Use this for quick visualization. For overlays and custom plots, + use plot_on_axis() instead. + + Args: + figsize: Figure size (width, height) in inches + power: Intensity power transform (1.0-5.0) + cmap: Matplotlib colormap name + show_colorbar: Whether to show colorbar + show_axes: Whether to show frequency/time axis labels + + Returns: + Matplotlib Figure object + """ + import matplotlib.pyplot as plt + + fig, ax = plt.subplots(figsize=figsize) + + # Use plot_on_axis internally + im = self.plot_on_axis(ax, power=power, cmap=cmap) + + if show_axes: + ax.set_xlabel('Time (s)') + ax.set_ylabel('Frequency (Hz)') + else: + ax.axis('off') + + if show_colorbar: + plt.colorbar(im, ax=ax, label='Intensity') + + return fig + + def save(self, filepath: str, width: Optional[int] = None, + height: Optional[int] = None, power: float = 1.0, + cmap: str = 'viridis', format: Optional[str] = None, + **kwargs): + """Save spectrogram as image file. + + Args: + filepath: Output file path + width: Output width in pixels (default: original) + height: Output height in pixels (default: original) + power: Intensity power transform (1.0-5.0) + cmap: Matplotlib colormap name + format: Image format ('png', 'jpg', 'webp', etc.) + Auto-detected from filepath extension if not provided + **kwargs: Additional arguments passed to PIL Image.save() + """ + img = self.to_image(width, height, power, cmap) + + # Auto-detect format from extension if not provided + if format is None: + suffix = Path(filepath).suffix + if suffix: + format = suffix[1:] # Remove leading dot + + img.save(filepath, format=format, **kwargs) + + @property + def shape(self) -> Tuple[int, int]: + """Data shape: (frequency_bins, time_frames).""" + return self._data.shape + + @property + def duration(self) -> float: + """Audio duration in seconds (estimated from time frames).""" + return self.shape[1] * self.time_resolution + + @property + def time_resolution(self) -> float: + """Time resolution in seconds per frame. + + Estimated based on typical CQT parameters for audio sampling. + """ + # Typical hop size for CQT is around 0.01s per frame + # This is an approximation - exact value depends on sample rate and hop length + # For 44100 Hz sample rate with hop_length=512: 512/44100 ≈ 0.0116s + return 0.0116 # seconds per frame (approximate) + + @property + def freq_bins(self) -> np.ndarray: + """Array of frequency values (Hz) for each bin. + + Calculated from bins_per_octave and freq_range using log spacing. + """ + n_bins = self.shape[0] + + # Calculate frequencies using constant-Q log spacing + # freq = min_freq * 2^(bin / bins_per_octave) + min_freq = self.freq_range[0] + bin_indices = np.arange(n_bins) + frequencies = min_freq * np.power(2, bin_indices / self.bins_per_octave) + + return frequencies diff --git a/idtap/tests/spectrogram_test.py b/idtap/tests/spectrogram_test.py new file mode 100644 index 0000000..05b731e --- /dev/null +++ b/idtap/tests/spectrogram_test.py @@ -0,0 +1,449 @@ +"""Tests for spectrogram data access and visualization.""" + +import os +import sys +import gzip +import json +import pytest +import numpy as np +from pathlib import Path +from unittest.mock import Mock, patch, MagicMock + +sys.path.insert(0, os.path.abspath('.')) + +from idtap.spectrogram import SpectrogramData, SUPPORTED_COLORMAPS + + +# Helper function to create mock spectrogram data +def create_mock_spec_data(freq_bins=368, time_frames=1000): + """Create mock spectrogram data for testing.""" + # Create synthetic spectrogram data (uint8) + data = np.random.randint(0, 256, size=(freq_bins, time_frames), dtype=np.uint8) + return data + + +def create_mock_compressed_data(freq_bins=368, time_frames=1000): + """Create mock compressed spectrogram data.""" + data = create_mock_spec_data(freq_bins, time_frames) + compressed = gzip.compress(data.tobytes()) + metadata = {"shape": [freq_bins, time_frames]} + return compressed, metadata + + +class TestSpectrogramDataInit: + """Test SpectrogramData initialization.""" + + def test_init_with_valid_data(self): + """Test initialization with valid numpy array.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_audio_id") + + assert spec.audio_id == "test_audio_id" + assert spec.shape == (368, 1000) + assert spec.freq_range == (75.0, 2400.0) + assert spec.bins_per_octave == 72 + + def test_init_with_custom_params(self): + """Test initialization with custom frequency range and bins.""" + data = create_mock_spec_data() + spec = SpectrogramData( + data, "test_id", + freq_range=(100.0, 2000.0), + bins_per_octave=60 + ) + + assert spec.freq_range == (100.0, 2000.0) + assert spec.bins_per_octave == 60 + + def test_init_rejects_non_numpy(self): + """Test that non-numpy array raises TypeError.""" + with pytest.raises(TypeError, match="data must be numpy array"): + SpectrogramData([[1, 2], [3, 4]], "test_id") + + def test_init_rejects_non_uint8(self): + """Test that non-uint8 dtype raises TypeError.""" + data = np.random.rand(100, 200).astype(np.float32) + with pytest.raises(TypeError, match="data must be uint8 array"): + SpectrogramData(data, "test_id") + + def test_init_rejects_non_2d(self): + """Test that non-2D array raises ValueError.""" + data = np.random.randint(0, 256, size=(100,), dtype=np.uint8) + with pytest.raises(ValueError, match="data must be 2D array"): + SpectrogramData(data, "test_id") + + +class TestSpectrogramDataLoading: + """Test loading spectrogram data from server.""" + + @patch('idtap.client.SwaraClient') + def test_from_audio_id_with_client(self, mock_client_class): + """Test loading from audio_id with provided client.""" + # Setup mock client + mock_client = Mock() + compressed, metadata = create_mock_compressed_data() + mock_client.download_spectrogram_data.return_value = compressed + mock_client.download_spectrogram_metadata.return_value = metadata + + # Load spectrogram + spec = SpectrogramData.from_audio_id("test_audio_id", mock_client) + + # Verify calls + mock_client.download_spectrogram_data.assert_called_once_with("test_audio_id") + mock_client.download_spectrogram_metadata.assert_called_once_with("test_audio_id") + + # Verify data + assert spec.audio_id == "test_audio_id" + assert spec.shape == (368, 1000) + + @patch('idtap.client.SwaraClient') + def test_from_audio_id_creates_client(self, mock_client_class): + """Test that from_audio_id creates client if not provided.""" + # Setup mock + mock_client = Mock() + compressed, metadata = create_mock_compressed_data() + mock_client.download_spectrogram_data.return_value = compressed + mock_client.download_spectrogram_metadata.return_value = metadata + mock_client_class.return_value = mock_client + + # Load without providing client + spec = SpectrogramData.from_audio_id("test_audio_id") + + # Verify client was created + mock_client_class.assert_called_once() + assert spec.audio_id == "test_audio_id" + + def test_from_piece_with_audio_id(self): + """Test loading from Piece object with audio_id.""" + # Create mock piece + mock_piece = Mock() + mock_piece.audio_id = "piece_audio_id" + + # Create mock client + mock_client = Mock() + compressed, metadata = create_mock_compressed_data() + mock_client.download_spectrogram_data.return_value = compressed + mock_client.download_spectrogram_metadata.return_value = metadata + + # Load from piece + spec = SpectrogramData.from_piece(mock_piece, mock_client) + + assert spec.audio_id == "piece_audio_id" + + def test_from_piece_without_audio_id(self): + """Test from_piece returns None when no audio_id.""" + mock_piece = Mock() + mock_piece.audio_id = None + + spec = SpectrogramData.from_piece(mock_piece) + + assert spec is None + + +class TestIntensityTransform: + """Test intensity power transformation.""" + + def test_apply_intensity_linear(self): + """Test power=1.0 returns unchanged data.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + result = spec.apply_intensity(power=1.0) + + np.testing.assert_array_equal(result, data) + + def test_apply_intensity_power_transform(self): + """Test power transform increases contrast.""" + # Create test data with known values + data = np.array([[100, 200]], dtype=np.uint8) + spec = SpectrogramData(data, "test_id") + + result = spec.apply_intensity(power=2.0) + + # Higher power should make dark values darker, bright values relatively brighter + assert result[0, 0] < data[0, 0] # 100 gets darker + assert result.dtype == np.uint8 + + def test_apply_intensity_range_validation(self): + """Test that power outside valid range raises ValueError.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + with pytest.raises(ValueError, match="Power must be between 1.0 and 5.0"): + spec.apply_intensity(power=0.5) + + with pytest.raises(ValueError, match="Power must be between 1.0 and 5.0"): + spec.apply_intensity(power=6.0) + + def test_apply_intensity_preserves_shape(self): + """Test that intensity transform preserves array shape.""" + data = create_mock_spec_data(100, 200) + spec = SpectrogramData(data, "test_id") + + result = spec.apply_intensity(power=2.5) + + assert result.shape == data.shape + + +class TestColormapApplication: + """Test colormap application.""" + + def test_apply_colormap_default(self): + """Test colormap with default viridis.""" + data = create_mock_spec_data(10, 20) + spec = SpectrogramData(data, "test_id") + + rgb = spec.apply_colormap() + + assert rgb.shape == (10, 20, 3) + assert rgb.dtype == np.uint8 + + def test_apply_colormap_custom(self): + """Test colormap with custom colormap name.""" + data = create_mock_spec_data(10, 20) + spec = SpectrogramData(data, "test_id") + + rgb = spec.apply_colormap(cmap='plasma') + + assert rgb.shape == (10, 20, 3) + + def test_apply_colormap_invalid_name(self): + """Test that invalid colormap name raises ValueError.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + with pytest.raises(ValueError, match="Unknown colormap"): + spec.apply_colormap(cmap='nonexistent_colormap') + + def test_apply_colormap_with_custom_data(self): + """Test applying colormap to custom data array.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + custom_data = np.ones((50, 100), dtype=np.uint8) * 128 + rgb = spec.apply_colormap(data=custom_data) + + assert rgb.shape == (50, 100, 3) + + +class TestCropping: + """Test frequency and time cropping.""" + + def test_crop_frequency(self): + """Test frequency cropping.""" + data = create_mock_spec_data(368, 1000) + spec = SpectrogramData(data, "test_id") + + # Crop to narrower frequency range + cropped = spec.crop_frequency(min_hz=200, max_hz=800) + + # Should have fewer frequency bins + assert cropped.shape[0] < spec.shape[0] + assert cropped.shape[1] == spec.shape[1] # Time unchanged + assert cropped.freq_range[0] >= 200 + assert cropped.freq_range[1] <= 800 + + def test_crop_time(self): + """Test time cropping.""" + data = create_mock_spec_data(368, 1000) + spec = SpectrogramData(data, "test_id") + + # Crop to 5-10 seconds + cropped = spec.crop_time(start_time=5.0, end_time=10.0) + + # Should have fewer time frames + assert cropped.shape[1] < spec.shape[1] + assert cropped.shape[0] == spec.shape[0] # Frequency unchanged + + def test_crop_chain(self): + """Test chaining crop operations.""" + data = create_mock_spec_data(368, 1000) + spec = SpectrogramData(data, "test_id") + + cropped = spec.crop_frequency(min_hz=200, max_hz=800).crop_time(start_time=2.0, end_time=8.0) + + assert cropped.shape[0] < spec.shape[0] + assert cropped.shape[1] < spec.shape[1] + + +class TestProperties: + """Test spectrogram properties.""" + + def test_shape_property(self): + """Test shape property.""" + data = create_mock_spec_data(100, 500) + spec = SpectrogramData(data, "test_id") + + assert spec.shape == (100, 500) + + def test_duration_property(self): + """Test duration calculation.""" + data = create_mock_spec_data(368, 1000) + spec = SpectrogramData(data, "test_id") + + duration = spec.duration + assert duration > 0 + assert isinstance(duration, float) + + def test_time_resolution_property(self): + """Test time resolution property.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + assert spec.time_resolution > 0 + assert spec.time_resolution < 0.1 # Should be in reasonable range + + def test_freq_bins_property(self): + """Test frequency bins calculation.""" + data = create_mock_spec_data(368, 1000) + spec = SpectrogramData(data, "test_id") + + freq_bins = spec.freq_bins + + assert len(freq_bins) == 368 + assert freq_bins[0] == spec.freq_range[0] # Min frequency + # Log spacing can slightly exceed max, allow 10% tolerance + assert freq_bins[-1] <= spec.freq_range[1] * 1.1 # Max frequency (with tolerance) + assert all(freq_bins[i] < freq_bins[i+1] for i in range(len(freq_bins)-1)) # Monotonic + + +class TestMatplotlibIntegration: + """Test matplotlib integration methods.""" + + def test_get_extent(self): + """Test get_extent returns correct format.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + extent = spec.get_extent() + + assert len(extent) == 4 + assert extent[0] == 0 # left (start time) + assert extent[1] == spec.duration # right (end time) + assert extent[2] == spec.freq_range[0] # bottom (min freq) + assert extent[3] == spec.freq_range[1] # top (max freq) + + def test_get_plot_data_without_colormap(self): + """Test get_plot_data without colormap.""" + data = create_mock_spec_data(100, 200) + spec = SpectrogramData(data, "test_id") + + plot_data, extent = spec.get_plot_data(power=2.0, apply_cmap=False) + + assert plot_data.shape == (100, 200) + assert plot_data.dtype == np.uint8 + assert len(extent) == 4 + + def test_get_plot_data_with_colormap(self): + """Test get_plot_data with colormap applied.""" + data = create_mock_spec_data(100, 200) + spec = SpectrogramData(data, "test_id") + + plot_data, extent = spec.get_plot_data(power=1.5, apply_cmap=True, cmap='plasma') + + assert plot_data.shape == (100, 200, 3) # RGB + assert plot_data.dtype == np.uint8 + + @patch('matplotlib.pyplot.subplots') + def test_plot_on_axis(self, mock_subplots): + """Test plotting on matplotlib axis.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + # Create mock axis + mock_ax = MagicMock() + mock_im = MagicMock() + mock_ax.imshow.return_value = mock_im + + # Plot on axis + im = spec.plot_on_axis(mock_ax, power=2.0, cmap='viridis', alpha=0.7) + + # Verify imshow was called + mock_ax.imshow.assert_called_once() + call_kwargs = mock_ax.imshow.call_args[1] + assert call_kwargs['cmap'] == 'viridis' + assert call_kwargs['alpha'] == 0.7 + assert call_kwargs['origin'] == 'lower' + assert call_kwargs['aspect'] == 'auto' + + +class TestImageGeneration: + """Test image generation methods.""" + + def test_to_image_basic(self): + """Test basic image generation.""" + data = create_mock_spec_data(100, 200) + spec = SpectrogramData(data, "test_id") + + img = spec.to_image() + + assert img.mode == 'RGB' + assert img.size == (200, 100) # PIL uses (width, height) + + def test_to_image_with_resize(self): + """Test image generation with resizing.""" + data = create_mock_spec_data(100, 200) + spec = SpectrogramData(data, "test_id") + + img = spec.to_image(width=400, height=200) + + assert img.size == (400, 200) + + def test_to_image_with_power(self): + """Test image generation with power transform.""" + data = create_mock_spec_data(100, 200) + spec = SpectrogramData(data, "test_id") + + img = spec.to_image(power=2.5, cmap='plasma') + + assert img.mode == 'RGB' + + @patch('matplotlib.pyplot.colorbar') + @patch('matplotlib.pyplot.subplots') + def test_to_matplotlib(self, mock_subplots, mock_colorbar): + """Test matplotlib figure generation.""" + data = create_mock_spec_data() + spec = SpectrogramData(data, "test_id") + + # Setup mocks + mock_fig = MagicMock() + mock_ax = MagicMock() + mock_im = MagicMock() + mock_ax.imshow.return_value = mock_im + mock_subplots.return_value = (mock_fig, mock_ax) + + fig = spec.to_matplotlib(power=2.0, cmap='viridis', show_colorbar=True) + + # Verify figure was created + mock_subplots.assert_called_once() + mock_ax.imshow.assert_called_once() + mock_colorbar.assert_called_once() + + def test_save(self, tmp_path): + """Test saving spectrogram to file.""" + data = create_mock_spec_data(50, 100) + spec = SpectrogramData(data, "test_id") + + filepath = tmp_path / "test_spec.png" + spec.save(str(filepath), power=1.5, cmap='viridis') + + assert filepath.exists() + + +class TestConstants: + """Test module-level constants.""" + + def test_supported_colormaps(self): + """Test that SUPPORTED_COLORMAPS list is defined.""" + assert isinstance(SUPPORTED_COLORMAPS, list) + assert len(SUPPORTED_COLORMAPS) > 0 + assert 'viridis' in SUPPORTED_COLORMAPS + assert 'plasma' in SUPPORTED_COLORMAPS + + def test_default_freq_range(self): + """Test default frequency range constant.""" + assert SpectrogramData.DEFAULT_FREQ_RANGE == (75.0, 2400.0) + + def test_default_bins_per_octave(self): + """Test default bins per octave constant.""" + assert SpectrogramData.DEFAULT_BINS_PER_OCTAVE == 72 diff --git a/pyproject.toml b/pyproject.toml index cccb14f..66bf594 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,10 @@ dependencies = [ "cryptography>=41.0.0", "PyJWT>=2.8.0", "google-auth-oauthlib>=1.0.0", - "pymongo>=4.0.0" + "pymongo>=4.0.0", + "numpy>=1.20.0", + "pillow>=9.0.0", + "matplotlib>=3.5.0" ] [project.optional-dependencies] From 3b30cfd153dcb3e521f63d0923e8c8ff499b118f Mon Sep 17 00:00:00 2001 From: Jon Myers Date: Thu, 6 Nov 2025 13:57:48 -0800 Subject: [PATCH 2/3] fix: add logarithmic frequency scale to spectrogram matplotlib plots - Add log_freq parameter to plot_on_axis() and to_matplotlib() (default: True) - Fixes issue where CQT log-spaced frequency bins were displayed with linear scale - Sets y-axis to log scale automatically for proper CQT representation --- idtap/spectrogram.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/idtap/spectrogram.py b/idtap/spectrogram.py index d9c52af..ef0c0eb 100644 --- a/idtap/spectrogram.py +++ b/idtap/spectrogram.py @@ -313,6 +313,7 @@ def plot_on_axis(self, ax: 'Axes', cmap: str = 'viridis', alpha: float = 1.0, zorder: int = 0, + log_freq: bool = True, **imshow_kwargs) -> 'AxesImage': """Plot spectrogram on an existing matplotlib axis (for overlays). @@ -325,6 +326,7 @@ def plot_on_axis(self, ax: 'Axes', cmap: Matplotlib colormap name alpha: Transparency (0.0-1.0), useful for subtle underlays zorder: Drawing order (0 = background, higher = foreground) + log_freq: Whether to use logarithmic frequency scale (default: True) **imshow_kwargs: Additional arguments passed to ax.imshow() Returns: @@ -353,6 +355,12 @@ def plot_on_axis(self, ax: 'Axes', **imshow_kwargs ) + # Set log scale for frequency axis (CQT is log-spaced) + if log_freq: + ax.set_yscale('log') + # Set reasonable y-axis limits + ax.set_ylim(self.freq_range[0], self.freq_range[1]) + return im def to_image(self, width: Optional[int] = None, @@ -417,7 +425,8 @@ def to_matplotlib(self, figsize: Tuple[float, float] = (12, 6), power: float = 1.0, cmap: str = 'viridis', show_colorbar: bool = True, - show_axes: bool = True) -> 'Figure': + show_axes: bool = True, + log_freq: bool = True) -> 'Figure': """Generate standalone matplotlib Figure for publication. Use this for quick visualization. For overlays and custom plots, @@ -429,6 +438,7 @@ def to_matplotlib(self, figsize: Tuple[float, float] = (12, 6), cmap: Matplotlib colormap name show_colorbar: Whether to show colorbar show_axes: Whether to show frequency/time axis labels + log_freq: Whether to use logarithmic frequency scale (default: True) Returns: Matplotlib Figure object @@ -438,7 +448,7 @@ def to_matplotlib(self, figsize: Tuple[float, float] = (12, 6), fig, ax = plt.subplots(figsize=figsize) # Use plot_on_axis internally - im = self.plot_on_axis(ax, power=power, cmap=cmap) + im = self.plot_on_axis(ax, power=power, cmap=cmap, log_freq=log_freq) if show_axes: ax.set_xlabel('Time (s)') From 1310310491f00f8ee82afff2235bc98446e8a643 Mon Sep 17 00:00:00 2001 From: Jon Myers Date: Thu, 6 Nov 2025 14:15:58 -0800 Subject: [PATCH 3/3] docs: add comprehensive spectrogram API documentation - Create docs/api/spectrogram.rst with SpectrogramData class documentation - Add usage examples for visualization and matplotlib overlays - Include technical details (CQT, frequency range, bins per octave) - Update docs/api/index.rst to include spectrogram in toctree and quick reference - Update docs/index.rst to list spectrogram feature --- docs/api/index.rst | 9 +++- docs/api/spectrogram.rst | 90 ++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 docs/api/spectrogram.rst diff --git a/docs/api/index.rst b/docs/api/index.rst index f7aa85c..b2b8dd5 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -22,6 +22,7 @@ Musical transcription data models: transcription-models audio-models + spectrogram Utilities --------- @@ -54,4 +55,10 @@ Audio Management * :class:`idtap.AudioMetadata` - Audio file metadata * :class:`idtap.AudioUploadResult` - Upload response -* :class:`idtap.Musician` - Performer information \ No newline at end of file +* :class:`idtap.Musician` - Performer information + +Spectrogram Analysis +~~~~~~~~~~~~~~~~~~~~ + +* :class:`idtap.SpectrogramData` - CQT spectrogram data and visualization +* :data:`idtap.SUPPORTED_COLORMAPS` - Available colormap names \ No newline at end of file diff --git a/docs/api/spectrogram.rst b/docs/api/spectrogram.rst new file mode 100644 index 0000000..735d8dc --- /dev/null +++ b/docs/api/spectrogram.rst @@ -0,0 +1,90 @@ +Spectrogram Analysis +==================== + +Spectrogram data access and visualization for audio analysis. + +.. currentmodule:: idtap + +SpectrogramData +--------------- + +The :class:`SpectrogramData` class provides comprehensive access to Constant-Q Transform (CQT) +spectrograms for computational musicology and audio analysis. + +.. autoclass:: SpectrogramData + :members: + :undoc-members: + :show-inheritance: + +Key Features +~~~~~~~~~~~~ + +* **Constant-Q Transform (CQT)** - Log-spaced frequency bins for musical analysis +* **Intensity Transformation** - Power-law contrast enhancement (1.0-5.0) +* **Colormap Support** - 35+ matplotlib colormaps +* **Frequency/Time Cropping** - Extract specific frequency ranges or time segments +* **Matplotlib Integration** - Plot on existing axes for overlays with pitch contours +* **Image Export** - Save as PNG, JPEG, WebP, etc. + +Quick Examples +~~~~~~~~~~~~~~ + +Load and display a spectrogram:: + + from idtap import SwaraClient, SpectrogramData + + client = SwaraClient() + spec = SpectrogramData.from_audio_id("audio_id_here", client) + + # Save basic visualization + spec.save("output.png", power=2.0, cmap='viridis') + +Create matplotlib overlay with pitch contour:: + + import matplotlib.pyplot as plt + + # Load spectrogram and piece data + spec = SpectrogramData.from_piece(piece, client) + + # Create figure + fig, ax = plt.subplots(figsize=(12, 6)) + + # Plot spectrogram as underlay with transparency + im = spec.plot_on_axis(ax, power=2.0, cmap='viridis', alpha=0.7, zorder=0) + + # Overlay pitch contour + times = [traj.start_time for traj in piece.trajectories] + pitches = [traj.pitch_contour[0] for traj in piece.trajectories] + ax.plot(times, pitches, 'r-', linewidth=2, zorder=1) + + # Configure axes + ax.set_xlabel('Time (s)') + ax.set_ylabel('Frequency (Hz)') + plt.colorbar(im, ax=ax, label='Intensity') + + plt.savefig('overlay.png', dpi=150, bbox_inches='tight') + +Crop to specific region:: + + # Extract 200-800 Hz range, first 10 seconds + cropped = spec.crop_frequency(200, 800).crop_time(0, 10) + cropped.save("cropped.png", power=2.5, cmap='magma') + +Supported Colormaps +~~~~~~~~~~~~~~~~~~~ + +.. autodata:: SUPPORTED_COLORMAPS + :annotation: + +Available colormaps include: viridis, plasma, magma, inferno, hot, cool, gray, and many more. +See the matplotlib colormap documentation for visual examples. + +Technical Details +~~~~~~~~~~~~~~~~~ + +* **Algorithm**: Essentia NSGConstantQ (Non-Stationary Gabor Constant-Q Transform) +* **Default Frequency Range**: 75-2400 Hz +* **Default Bins Per Octave**: 72 (high resolution for microtonal analysis) +* **Data Format**: uint8 grayscale (0-255), gzip-compressed +* **Time Resolution**: ~0.0116 seconds per frame (typical) +* **Frequency Scale**: Logarithmic (perceptually-uniform for music) diff --git a/docs/index.rst b/docs/index.rst index 8bed57f..9620eb9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,6 +45,7 @@ Features * **OAuth Authentication** - Secure Google OAuth integration with token storage * **Rich Data Models** - Comprehensive classes for musical transcription data * **Audio Management** - Upload, download, and manage audio files +* **Spectrogram Analysis** - CQT spectrogram visualization with matplotlib integration * **Export Capabilities** - Export transcriptions to JSON and Excel formats * **Permissions System** - Manage public/private visibility and sharing