Repository navigation
Bulk uploads ingest supplied supervoxel and root id columns - #271
Merged
Merged
Conversation
A source may carry <point>_supervoxel_id and <point>_root_id columns for its bound spatial points. They are written to a second processed CSV and imported into the segmentation table (load_segmentation), and the upload skips the lookups they make unnecessary: - points with supplied supervoxels skip the spatial lookup; their roots are looked up by id range of the segmentation table (resolve_roots, checkpointed in upload_root_chunks) - supplied roots are all checked with is_latest_roots at the job's timestamp (and for being top-layer ids); stale ones are looked up again - blank supervoxels in a supplied column are looked up from the point's position Ids are read as text (CSV) or exact Python ints (nullable parquet) so 64-bit ids are not rounded through float64; ids as large floats are refused. The wizard offers the optional id columns for bound spatial points (Ray uploads only), asks when supplied roots were looked up, and the preview checks the first rows' ids against the chunkedgraph. Also fixes the root lookup's timestamp parse when the microseconds are 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…chunks for 8 CPUs Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…000 ids kvdbclient reads one Bigtable request at a time, so a task mostly waited (~270 supervoxels/s). PyChunkedGraph splits reads into pieces of 1000 keys on threads; doing the same per task gave, on a 1-CPU pod on ltv7: get_roots 270/s -> 1548/s (8 threads), 2457/s (32); is_latest_roots 1523/s -> 15136/s (8). UPLOAD_ROOT_THREADS sets it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…9's is_latest_roots kvdbclient before 0.8 counts any id without a NewParent cell as latest, so a root-shaped id that never existed passed the supplied-root check. 0.9 also requires Child cells before the timestamp (PyChunkedGraph's rule), but 0.8+ needs Python 3.13 and the image is on 3.12, so the rule is read here with the same read_nodes call. On ltv7 it agreed with the old check on 19,769 real roots and refused a fabricated one, ~13% slower. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bulk uploads can now take supervoxel and root id columns from the source file, and skip the lookups those columns make unnecessary.
What changes for users
In step 2 of the upload wizard, each bound spatial point (
pre_pt,post_pton a synapse table) gets two optional rows:<point>_supervoxel_idand<point>_root_id. Plain spatial points (ctr_pt) don't get them, because they have no segmentation columns. These rows only appear on deployments that run Ray uploads; a Celery upload with these columns mapped is refused at start.When a root column is mapped, step 3 asks when the roots were looked up: at one time, sometime between two times, or not known. Step 4's preview checks the first rows' ids against the chunkedgraph and reports how many match, which catches a column mapped to the wrong point.
How it works
Separate segmentation CSV: processing writes the supplied ids to a second CSV (
<processed>.segmentation.csv), and a newload_segmentationstage imports it into the segmentation table with Cloud SQL.Exact ids: CSV id columns are read as text, and nullable parquet integers as Python ints, so 64-bit ids aren't rounded through float64. A file that stores ids as large floats is refused.
Stages per case:
resolve_rootsresolve_roots:upload_root_chunkstable. It is a new table only, so the schema version stays 1. Chunks also pause for frozen materialization like spatial chunks do.Every supplied root is checked at the job's timestamp, whatever time the user gives. The table is published as correct at that timestamp, and the live update only fixes roots that expire after it, so a root already stale then would otherwise stay wrong. A root is current only if it's a top-layer id that existed and had no new parent before the timestamp. That is kvdbclient 0.9's
is_latest_rootsrule, written out here because kvdbclient ≥0.8 needs Python 3.13. Older kvdbclient counted ids that never existed as current.Parallel lookups: chunkedgraph calls go in pieces of 1,000 ids, 8 at a time per task (
UPLOAD_ROOT_THREADS). kvdbclient sends one Bigtable request at a time. On a 1-CPU pod, threading tookget_rootsfrom 270 to 1,548 supervoxels a second, andis_latest_rootsfrom 1,523 to 15,136 roots a second.Also fixed: the root lookup's timestamp parsing failed when the microseconds were 0.
Tested on ltv7
The test files are the 1M synapse file, with ids taken from
synapses_1m_inprod6. For the root files, the roots were either current or from 90 days earlier:resolve_roots† On a cold cluster; 90d1 ran on workers left up by the run before it.
* About 4 minutes of that was the Ray autoscaler adding workers. Once they were up, the last 48 of 51 chunks finished in about 80 seconds. For comparison, the normal path (spatial lookup) for the same file is 9m07s.
In every table:
get_roots(supervoxel, job timestamp);upl…tables are left over.The 90-day file's stale roots match the count built into the file (40,603), plus newer edits.
Unit and integration tests: 25 new tests in
tests/test_upload_supplied_ids.py, and the full suite passes (820 passed, 2 skipped).Notes
finish_tables, exactly like the annotation table.resolve_roots, so the CLUSTER infinish_tablesrewrites the table. With supplied roots, only stale rows change.🤖 Generated with Claude Code