working aligned dynamic table and TimeSeriesReferenceVectorData

This commit is contained in:
sneakers-the-rat 2024-08-12 22:57:00 -07:00
parent dd99ac24eb
commit 06a18c23a8
Signed by untrusted user who does not match committer: jonny
GPG key ID: 6DCB96EF1E4D232D
9 changed files with 307 additions and 59 deletions

View file

@ -5,7 +5,7 @@
groups = ["default", "dev", "plot", "tests"] groups = ["default", "dev", "plot", "tests"]
strategy = ["inherit_metadata"] strategy = ["inherit_metadata"]
lock_version = "4.5.0" lock_version = "4.5.0"
content_hash = "sha256:903c3aeebf0fb234263b45213693f0eaee7ac290d22633b1d7a4d5aff51d032b" content_hash = "sha256:ed633a147948a9923f6b3a99690d5d8bad0b4b8c0d528abe62d132b05d1d9f39"
[[metadata.targets]] [[metadata.targets]]
requires_python = ">=3.10,<3.13" requires_python = ">=3.10,<3.13"
@ -1036,7 +1036,7 @@ files = [
[[package]] [[package]]
name = "numpydantic" name = "numpydantic"
version = "1.3.0" version = "1.3.1"
requires_python = "<4.0,>=3.9" requires_python = "<4.0,>=3.9"
summary = "Type and shape validation and serialization for numpy arrays in pydantic models" summary = "Type and shape validation and serialization for numpy arrays in pydantic models"
groups = ["default"] groups = ["default"]
@ -1046,8 +1046,8 @@ dependencies = [
"typing-extensions>=4.11.0; python_version < \"3.11\"", "typing-extensions>=4.11.0; python_version < \"3.11\"",
] ]
files = [ files = [
{file = "numpydantic-1.3.0-py3-none-any.whl", hash = "sha256:bda3aa2cd858e9211006be8b8e589e1905b2c6a2db17cec0c28563ba1ad66b68"}, {file = "numpydantic-1.3.1-py3-none-any.whl", hash = "sha256:c0a37c093fcd0e4ed52c4556f4e804eec76fcf924c546e475509e662336f9f61"},
{file = "numpydantic-1.3.0.tar.gz", hash = "sha256:b3931d51ba7e22d48bdd2ae56cad368f63db99ef74e8570021a7fd176b2ffc1f"}, {file = "numpydantic-1.3.1.tar.gz", hash = "sha256:d61868d7912f2dfee9906bd989399d74f470dee10d5028409c2f5d39529fc4af"},
] ]
[[package]] [[package]]
@ -1831,29 +1831,29 @@ files = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.5.6" version = "0.5.7"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust." summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"}, {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"},
{file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"}, {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"},
{file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"}, {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"},
{file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"}, {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"},
{file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"}, {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"},
{file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"}, {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"},
{file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"}, {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"},
] ]
[[package]] [[package]]
@ -2090,44 +2090,44 @@ files = [
[[package]] [[package]]
name = "watchdog" name = "watchdog"
version = "4.0.1" version = "4.0.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Filesystem events monitoring" summary = "Filesystem events monitoring"
groups = ["default"] groups = ["default"]
files = [ files = [
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"},
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"},
{file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"},
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"},
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"},
{file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"},
{file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"},
{file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"},
{file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"},
] ]
[[package]] [[package]]
name = "webcolors" name = "webcolors"
version = "24.6.0" version = "24.8.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "A library for working with the color formats defined by HTML and CSS." summary = "A library for working with the color formats defined by HTML and CSS."
groups = ["default"] groups = ["default"]
files = [ files = [
{file = "webcolors-24.6.0-py3-none-any.whl", hash = "sha256:8cf5bc7e28defd1d48b9e83d5fc30741328305a8195c29a8e668fa45586568a1"}, {file = "webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"},
{file = "webcolors-24.6.0.tar.gz", hash = "sha256:1d160d1de46b3e81e58d0a280d0c78b467dc80f47294b91b1ad8029d2cedb55b"}, {file = "webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d"},
] ]
[[package]] [[package]]
@ -2187,11 +2187,11 @@ files = [
[[package]] [[package]]
name = "zipp" name = "zipp"
version = "3.19.2" version = "3.20.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Backport of pathlib-compatible object wrapper for zip files" summary = "Backport of pathlib-compatible object wrapper for zip files"
groups = ["dev", "plot", "tests"] groups = ["dev", "plot", "tests"]
files = [ files = [
{file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"},
{file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"},
] ]

View file

@ -20,7 +20,7 @@ dependencies = [
"pydantic-settings>=2.0.3", "pydantic-settings>=2.0.3",
"tqdm>=4.66.1", "tqdm>=4.66.1",
'typing-extensions>=4.12.2;python_version<"3.11"', 'typing-extensions>=4.12.2;python_version<"3.11"',
"numpydantic>=1.3.0", "numpydantic>=1.3.1",
"black>=24.4.2", "black>=24.4.2",
"pandas>=2.2.2", "pandas>=2.2.2",
] ]

View file

@ -153,8 +153,8 @@ class Adapter(BaseModel):
# SchemaAdapters that should be located under the same # SchemaAdapters that should be located under the same
# NamespacesAdapter when it's important to query across SchemaAdapters, # NamespacesAdapter when it's important to query across SchemaAdapters,
# so skip to avoid combinatoric walking # so skip to avoid combinatoric walking
# if key == "imports" and type(input).__name__ == "SchemaAdapter": if key == "imports" and type(input).__name__ == "SchemaAdapter":
# continue continue
val = getattr(input, key) val = getattr(input, key)
yield (key, val) yield (key, val)
if isinstance(val, (BaseModel, dict, list)): if isinstance(val, (BaseModel, dict, list)):

View file

@ -26,7 +26,13 @@ from linkml_runtime.utils.compile_python import file_text
from linkml_runtime.utils.formatutils import remove_empty_items from linkml_runtime.utils.formatutils import remove_empty_items
from linkml_runtime.utils.schemaview import SchemaView from linkml_runtime.utils.schemaview import SchemaView
from nwb_linkml.includes.hdmf import DYNAMIC_TABLE_IMPORTS, DYNAMIC_TABLE_INJECTS from nwb_linkml.includes.base import BASEMODEL_GETITEM
from nwb_linkml.includes.hdmf import (
DYNAMIC_TABLE_IMPORTS,
DYNAMIC_TABLE_INJECTS,
TSRVD_IMPORTS,
TSRVD_INJECTS,
)
from nwb_linkml.includes.types import ModelTypeString, NamedImports, NamedString, _get_name from nwb_linkml.includes.types import ModelTypeString, NamedImports, NamedString, _get_name
OPTIONAL_PATTERN = re.compile(r"Optional\[([\w\.]*)\]") OPTIONAL_PATTERN = re.compile(r"Optional\[([\w\.]*)\]")
@ -44,6 +50,7 @@ class NWBPydanticGenerator(PydanticGenerator):
' is stored in an NWB file")' ' is stored in an NWB file")'
), ),
'object_id: Optional[str] = Field(None, description="Unique UUID for each object")', 'object_id: Optional[str] = Field(None, description="Unique UUID for each object")',
BASEMODEL_GETITEM,
) )
split: bool = True split: bool = True
imports: list[Import] = field(default_factory=lambda: [Import(module="numpy", alias="np")]) imports: list[Import] = field(default_factory=lambda: [Import(module="numpy", alias="np")])
@ -232,7 +239,7 @@ class AfterGenerateClass:
Returns: Returns:
""" """
if cls.cls.name == "DynamicTable": if cls.cls.name in "DynamicTable":
cls.cls.bases = ["DynamicTableMixin"] cls.cls.bases = ["DynamicTableMixin"]
if cls.injected_classes is None: if cls.injected_classes is None:
@ -254,6 +261,21 @@ class AfterGenerateClass:
cls.cls.bases = ["DynamicTableRegionMixin", "VectorData"] cls.cls.bases = ["DynamicTableRegionMixin", "VectorData"]
elif cls.cls.name == "AlignedDynamicTable": elif cls.cls.name == "AlignedDynamicTable":
cls.cls.bases = ["AlignedDynamicTableMixin", "DynamicTable"] cls.cls.bases = ["AlignedDynamicTableMixin", "DynamicTable"]
elif cls.cls.name == "TimeSeriesReferenceVectorData":
# in core.nwb.base, so need to inject and import again
cls.cls.bases = ["TimeSeriesReferenceVectorDataMixin", "VectorData"]
if cls.injected_classes is None:
cls.injected_classes = TSRVD_INJECTS.copy()
else:
cls.injected_classes.extend(TSRVD_INJECTS.copy())
if isinstance(cls.imports, Imports):
cls.imports += TSRVD_IMPORTS
elif isinstance(cls.imports, list):
cls.imports = Imports(imports=cls.imports) + TSRVD_IMPORTS
else:
cls.imports = TSRVD_IMPORTS.model_copy()
return cls return cls

View file

@ -0,0 +1,14 @@
"""
Modifications to the ConfiguredBaseModel used by all generated classes
"""
BASEMODEL_GETITEM = """
def __getitem__(self, val: Union[int, slice]) -> Any:
\"\"\"Try and get a value from value or "data" if we have it\"\"\"
if hasattr(self, "value") and self.value is not None:
return self.value[val]
elif hasattr(self, "data") and self.data is not None:
return self.data[val]
else:
raise KeyError("No value or data field to index from")
"""

View file

@ -535,6 +535,109 @@ class AlignedDynamicTableMixin(DynamicTableMixin):
df.set_index((self.name, "id"), drop=True, inplace=True) df.set_index((self.name, "id"), drop=True, inplace=True)
return df return df
@model_validator(mode="before")
@classmethod
def create_categories(cls, model: Dict[str, Any]) -> Dict:
"""
Construct categories from arguments.
the model dict is ordered after python3.6, so we can use that minus
anything in :attr:`.NON_COLUMN_FIELDS` to determine order implied from passage order
"""
if "categories" not in model:
categories = [
k for k in model if k not in cls.NON_CATEGORY_FIELDS and not k.endswith("_index")
]
model["categories"] = categories
else:
# add any columns not explicitly given an order at the end
categories = [
k
for k in model
if k not in cls.NON_COLUMN_FIELDS
and not k.endswith("_index")
and k not in model["categories"]
]
model["categories"].extend(categories)
return model
class TimeSeriesReferenceVectorDataMixin(VectorDataMixin):
"""
Mixin class for TimeSeriesReferenceVectorData -
very simple, just indexing the given timeseries object.
These shouldn't have additional fields in them, just the three columns
for index, span, and timeseries
"""
idx_start: NDArray[Any, int]
count: NDArray[Any, int]
timeseries: NDArray[Any, BaseModel]
@model_validator(mode="after")
def ensure_equal_length(self) -> "TimeSeriesReferenceVectorDataMixin":
assert len(self.idx_start) == len(self.timeseries) == len(self.count), (
f"Columns have differing lengths: idx: {len(self.idx_start)}, count: {len(self.count)},"
f" timeseries: {len(self.timeseries)}"
)
return self
def __len__(self) -> int:
"""Since we have ensured equal length, just return idx_start"""
return len(self.idx_start)
@overload
def _slice_helper(self, item: int) -> slice: ...
@overload
def _slice_helper(self, item: slice) -> List[slice]: ...
def _slice_helper(self, item: Union[int, slice]) -> Union[slice, List[slice]]:
if isinstance(item, (int, np.integer)):
return slice(self.idx_start[item], self.idx_start[item] + self.count[item])
else:
starts = self.idx_start[item]
ends = starts + self.count[item]
return [slice(start, end) for start, end in zip(starts, ends)]
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self._index is not None:
raise NotImplementedError(
"VectorIndexing with TimeSeriesReferenceVectorData is not supported because it is"
" never done in the core schema."
)
if isinstance(item, (int, np.integer)):
return self.timeseries[self._slice_helper(item)]
elif isinstance(item, slice):
return [self.timeseries[subitem] for subitem in self._slice_helper(item)]
elif isinstance(item, Iterable):
return [self.timeseries[self._slice_helper(subitem)] for subitem in item]
else:
raise ValueError(
f"Dont know how to index with {item}, must be an int, slice, or iterable"
)
def __setitem__(self, key: Union[int, slice, Iterable], value: Any) -> None:
if self._index is not None:
raise NotImplementedError(
"VectorIndexing with TimeSeriesReferenceVectorData is not supported because it is"
" never done in the core schema."
)
if isinstance(key, (int, np.integer)):
self.timeseries[self._slice_helper(key)] = value
elif isinstance(key, slice):
for subitem in self._slice_helper(key):
self.timeseries[subitem] = value
elif isinstance(key, Iterable):
for subitem in key:
self.timeseries[self._slice_helper(subitem)] = value
else:
raise ValueError(
f"Dont know how to index with {key}, must be an int, slice, or iterable"
)
DYNAMIC_TABLE_IMPORTS = Imports( DYNAMIC_TABLE_IMPORTS = Imports(
imports=[ imports=[
@ -577,3 +680,19 @@ DYNAMIC_TABLE_INJECTS = [
DynamicTableMixin, DynamicTableMixin,
AlignedDynamicTableMixin, AlignedDynamicTableMixin,
] ]
TSRVD_IMPORTS = Imports(
imports=[
Import(
module="typing",
objects=[
ObjectImport(name="overload"),
ObjectImport(name="Iterable"),
ObjectImport(name="Tuple"),
],
),
Import(module="pydantic", objects=[ObjectImport(name="model_validator")]),
]
)
"""Imports for TimeSeriesReferenceVectorData"""
TSRVD_INJECTS = [VectorDataMixin, TimeSeriesReferenceVectorDataMixin]

View file

@ -42,6 +42,7 @@ def test_walk_fields(nwb_core_fixture):
dtype = list(nwb_core_fixture.walk_fields(nwb_core_fixture, "dtype")) dtype = list(nwb_core_fixture.walk_fields(nwb_core_fixture, "dtype"))
dtype_havers = list(nwb_core_fixture.walk_types(nwb_core_fixture, (Dataset, Attribute))) dtype_havers = list(nwb_core_fixture.walk_types(nwb_core_fixture, (Dataset, Attribute)))
dtype_havers = [haver for haver in dtype_havers if haver.dtype is not None]
compound_dtypes = [len(d.dtype) for d in dtype_havers if isinstance(d.dtype, list)] compound_dtypes = [len(d.dtype) for d in dtype_havers if isinstance(d.dtype, list)]
expected_dtypes = np.sum(compound_dtypes) + len(dtype_havers) expected_dtypes = np.sum(compound_dtypes) + len(dtype_havers)
assert expected_dtypes == len(dtype) assert expected_dtypes == len(dtype)

View file

@ -15,6 +15,11 @@ from nwb_linkml.models import (
IntracellularResponsesTable, IntracellularResponsesTable,
IntracellularStimuliTable, IntracellularStimuliTable,
IntracellularRecordingsTable, IntracellularRecordingsTable,
VoltageClampSeries,
VoltageClampSeriesData,
VoltageClampStimulusSeries,
VoltageClampStimulusSeriesData,
TimeSeriesReferenceVectorData,
) )
@ -102,19 +107,68 @@ def units(request) -> Tuple[Units, list[np.ndarray], np.ndarray]:
return units, spike_times, spike_idx return units, spike_times, spike_idx
def _icephys_stimulus_and_response(
i: int, electrode: IntracellularElectrode
) -> tuple[VoltageClampStimulusSeries, VoltageClampSeries]:
generator = np.random.default_rng()
n_samples = generator.integers(20, 50)
stimulus = VoltageClampStimulusSeries(
name=f"vcss_{i}",
data=VoltageClampStimulusSeriesData(value=[i] * n_samples),
stimulus_description=f"{i}",
sweep_number=i,
electrode=electrode,
)
response = VoltageClampSeries(
name=f"vcs_{i}",
data=VoltageClampSeriesData(value=[i] * n_samples),
stimulus_description=f"{i}",
electrode=electrode,
)
return stimulus, response
@pytest.fixture() @pytest.fixture()
def intracellular_recordings_table() -> IntracellularRecordingsTable: def intracellular_recordings_table() -> IntracellularRecordingsTable:
n_recordings = 10 n_recordings = 10
generator = np.random.default_rng()
device = Device(name="my device") device = Device(name="my device")
electrode = IntracellularElectrode( electrode = IntracellularElectrode(
name="my_electrode", description="an electrode", device=device name="my_electrode", description="an electrode", device=device
) )
stims = []
responses = []
for i in range(n_recordings):
stim, response = _icephys_stimulus_and_response(i, electrode)
stims.append(stim)
responses.append(response)
electrodes = IntracellularElectrodesTable( electrodes = IntracellularElectrodesTable(
name="intracellular_electrodes", electrode=[electrode] * n_recordings name="intracellular_electrodes", electrode=[electrode] * n_recordings
) )
stimuli = IntracellularStimuliTable( stimuli = IntracellularStimuliTable(
name="intracellular_stimuli", name="intracellular_stimuli",
stimulus=TimeSeriesReferenceVectorData(
name="stimulus",
description="this should be optional",
idx_start=np.arange(n_recordings),
count=generator.integers(1, 10, (n_recordings,)),
timeseries=stims,
),
) )
responses = IntracellularResponsesTable()
recordings_table = IntracellularRecordingsTable() responses = IntracellularResponsesTable(
name="intracellular_responses",
response=TimeSeriesReferenceVectorData(
name="response",
description="this should be optional",
idx_start=np.arange(n_recordings),
count=generator.integers(1, 10, (n_recordings,)),
timeseries=responses,
),
)
recordings_table = IntracellularRecordingsTable(
electrodes=electrodes, stimuli=stimuli, responses=responses
)
return recordings_table

View file

@ -1,4 +1,5 @@
import numpy as np import numpy as np
import pandas as pd
# FIXME: Make this just be the output of the provider by patching into import machinery # FIXME: Make this just be the output of the provider by patching into import machinery
from nwb_linkml.models.pydantic.core.v2_7_0.namespace import ( from nwb_linkml.models.pydantic.core.v2_7_0.namespace import (
@ -6,6 +7,7 @@ from nwb_linkml.models.pydantic.core.v2_7_0.namespace import (
DynamicTableRegion, DynamicTableRegion,
ElectrodeGroup, ElectrodeGroup,
VectorIndex, VectorIndex,
VoltageClampStimulusSeries,
) )
from .conftest import _ragged_array from .conftest import _ragged_array
@ -159,3 +161,39 @@ def test_dynamictable_extra_coercion():
Extra fields should be coerced to VectorData and have their Extra fields should be coerced to VectorData and have their
indexing relationships handled when passed as plain arrays. indexing relationships handled when passed as plain arrays.
""" """
def test_aligned_dynamictable(intracellular_recordings_table):
"""
Multiple aligned dynamictables should be indexable with a multiindex
"""
# can get a single row.. (check correctness below)
row = intracellular_recordings_table[0]
# can get a single table with its name
stimuli = intracellular_recordings_table["stimuli"]
assert stimuli.shape == (10, 1)
# nab a few rows to make the dataframe
rows = intracellular_recordings_table[0:3]
assert all(
rows.columns
== pd.MultiIndex.from_tuples(
[
("electrodes", "index"),
("electrodes", "electrode"),
("stimuli", "index"),
("stimuli", "stimulus"),
("responses", "index"),
("responses", "response"),
]
)
)
# ensure that we get the actual values from the TimeSeriesReferenceVectorData
# also tested separately
# each individual cell should be an array of VoltageClampStimulusSeries...
# and then we should be able to index within that as well
stims = rows["stimuli", "stimulus"][0]
for i in range(len(stims)):
assert isinstance(stims[i], VoltageClampStimulusSeries)
assert all([i == val for val in stims[i][:]])