Publish the paper's Stage 1 inputs, and plan the rest onto Hugging Face - #109
Open
jonfroehlich wants to merge 15 commits into
Open
Publish the paper's Stage 1 inputs, and plan the rest onto Hugging Face#109jonfroehlich wants to merge 15 commits into
jonfroehlich wants to merge 15 commits into
Conversation
… rows they became v1.0-iccv2025 shipped the code that consumes the three open-government curb ramp inventories but not the files themselves: stage_one/dataset_generation/.gitignore carried a location_data/* line. That gap is not fixable by re-downloading -- the city portals serve current data and it drifts (Bend +8.7%) -- so the paper's actual inputs existed only on a cluster scratch directory. Commits all three (71.8 MB, hash-pinned in docs/data_provenance.md section 3) and marks them binary in .gitattributes, so no contributor's core.autocrlf can quietly invalidate those hashes. Adds scripts/analysis/gov_provenance.py, which answers the question all_locations.csv cannot. combine_location_data.py reduces the inventories to (latitude, longitude, date) and shuffles, discarding NYC's RampID/CornerID, the geojson OBJECTID/FacilityID/ NonAssetID, and even the city label. The script recovers the mapping by coordinate join and verifies it: 276,071/276,071 rows resolved, 0 unmatched, 156,712 (56.77%) consumed by a generated panorama. So 43.23% of the government records never became a label. Three findings that constrain any Stage 1 re-run, documented beside the numbers: - The paper-era shuffle was UNSEEDED -- random.seed(42) was added later -- so the published row order is gone for good. Hence a coordinate join, not a replay. - 8 coordinates are shared by two government records, making 16 rows ambiguous, and that is necessarily the whole of the residual date disagreement. - convert_date changed after the paper: an unknown date was "2000-01-01", now "". Measured, that is 23,088 records (8.36%) with no install date, every one of which the paper's run silently admitted to the installed-before-capture check. street_data/ (801 MB) and the Stage 1 manifests stay out -- "New York - Streets.geojson" alone is 669 MB, past GitHub's 100 MB hard limit. Both are now logged as explicit blockers in docs/replication.md for #21. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
street_data/ was the last Stage 1 input that could not go in git: 801.6 MB, and
"New York - Streets.geojson" alone is 669 MB, past GitHub's 100 MB hard limit.
It turns out almost none of that is used. Its only consumer, generate_negative_panos.py,
reads the LineString geometry plus exactly ONE name property, and only as an emptiness
test -- FULLNAME (Bend), FULL_NAME (Portland), Street (New York). Route numbers, ZIP,
MSAG, ESN, one-way direction, road class and county are never touched.
scripts/build_street_derivative.py strips the rest:
Bend 8,934,761 -> 553,314 16.1x
Portland 123,616,787 -> 8,238,233 15.0x
New York 669,049,016 -> 9,908,856 67.5x
TOTAL 801,600,564 -> 18,700,403 42.9x
The name field is kept rather than dropped, because Portland has 4,192 features whose
FULL_NAME is empty and which load_city_streets skips -- a pure-geometry file would
silently re-admit them and change the sampled network.
Equivalence is proved, not asserted. `verify` computes a consumer fingerprint: sha256
over the ordered (name, geometry) pairs of every feature surviving the filter, which is
exactly what the length-weighted sampling index is built from. All three match, and the
same fingerprints reproduce on Windows and on klone.
generate_negative_panos.py prefers a full download when present and falls back to the
derivative, so existing checkouts behave identically.
Priority note recorded in docs/replication.md: street data is NOT on the critical path
for reproducing the paper. generate_negative_panos.py is unseeded, so the negatives
cannot be regenerated from any street file. What reproduces them is the manifest --
negativepanosSHORTENED.jsonl (5.2 MB) and finaldataset.jsonl (64 MB). Those are the
smaller, higher-value publish and should lead #21.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… lives Answers "what goes on HF, and in what order" with measured sizes, plus a README table so no input is discoverable only by asking us. Everything lands in the projectsidewalk/rampnet collection: three repos exist, three are new, and one existing repo gains a folder. Under 29 GB of new upload against the 463 GB already hosted, so capacity is not a consideration. Ordering is driven by what blocks what, and the top item was not the obvious one: - rampnet-crop-model (720.7 MB) FIRST. inference_isolator.py hardcodes a path to the round-2 crop checkpoint -- it is what turns every government GPS coordinate into a pixel keypoint -- and it exists only on lab storage. Publishing location_data without it hands someone the inputs to a pipeline they still cannot run. Both rounds go up, since round 1 initialises round 2. Verified by hash while inventorying: ps_model.pth is byte-identical to the round-1 checkpoint, so the README's "copy it here, renamed" step is now confirmed rather than assumed, and only two distinct files need uploading. - rampnet-stage1-inputs (1.06 GB), manifests first. finaldataset.jsonl and negativepanosSHORTENED.jsonl are the ONLY way to reproduce the paper's negatives, because the sampler is unseeded. The source files are archive; the manifests are the reproduction path. - rampnet-benchmark (~12 GB, #21) incrementally, not held back for more cities. Benchmark imagery is immutable once fetched and the revisable parts live in git, so the set only grows; a new city is one folder and hf upload skips matching hashes. - The 15 GB round-1 Project Sidewalk crop set joins rampnet-crop-model-dataset as Parquet under round1_ps/. Parquet is forced, not chosen: 27,710 entries is past the 10,000-per-folder hard limit. The 1,212 round-2 crops stay loose -- converting them would orphan LFS versions and break existing paths for a cosmetic gain. Checked the two live repos against the terms we agreed in July 2025 rather than assuming we had followed them: rampnet-dataset is Parquet in 128 shards per split, exactly as advised; rampnet-crop-model-dataset is 1,214 loose JPEGs, which is under the per-folder limit but not the recommended shape. That is what surfaced the constraint above. Renames the imagery folders panos_native/ and panos_4096x2048/. The earlier panos_model_res/ is a poor public name twice over: "res" is unexplained, and "model resolution" is relative, so it silently goes wrong when the input size changes -- a live risk with #25 and #20 open. Layout is the one thing worth settling before the first upload, since restructuring means replacing large blobs, the one expensive HF operation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… of estimating it
Two loose ends from the publishing plan.
scripts/export_crop_model.py packages the Stage 1 crop checkpoints for HF, mirroring
scripts/export_hf_model.py. Nothing uploads without --push; expected sha256 values are
asserted before anything is copied, and the copies are re-hashed afterwards so a push
cannot ship bytes that were never verified. It also refuses to run if both inputs hash
the same, which is the easy mistake here -- ps_model.pth sits beside the round-2
checkpoint and is a byte-identical copy of round 1.
The panos_4096x2048 size was the one number in the plan that was an estimate. Rendered
all 1,109 panoramas across the 9 splits at 4096x2048 / q82 / BILINEAR -- byte-for-byte
what gt_gallery.py applies, deliberately NOT using PIL draft(), which is much faster on
the 16384px natives but yields different pixels and would defeat the purpose.
Measured 1.02 GB, against 10.89 GB native. The estimate said 1-2 GB, so the benchmark
repo lands at 12.1 GB.
The per-split spread is the more useful result, and it corrects how I had been
describing this folder:
gainesville/paterson/sao_paulo/bend/clovis 16-18x smaller (13312-16384 px)
annapolis/richmond 3.6-3.8x (8000-12288 px)
budapest_district5 1.6x (4096-5760 px)
morgantown 0.9x (4096-5760 px)
Morgantown's derivative is LARGER than its native archive -- those panoramas are already
at model resolution and heavily compressed, so re-encoding costs a JPEG generation and
buys nothing. That is not a reason to drop it, since gt_gallery.py re-encodes at q82
regardless and this is therefore still what the reviewer saw. But it does mean the
folder is a review-fidelity artifact, not "the small one", and the docs now say so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jonfroehlich
force-pushed
the
feat/publish-stage1-inputs
branch
from
August 4, 2026 17:44
10f1359 to
a4373c1
Compare
…constraint
Nothing in the publishing plan is within an order of magnitude of a limit:
single file 669 MB vs 500 GB hard / 200 GB recommended
entries/folder 125 vs 10,000 hard
files per repo ~2,550 vs <100k recommended
commit size auto-split by upload_folder
The per-city subdirectory layout is what keeps the folder count trivial, which is the
one decision that could have gone wrong.
Corrects a claim I made earlier: I said 27,710 round-1 crop files "exceed the 10k hard
limit, so loose files are not an option". The limit is per FOLDER, not per repo, and the
crop set already has train/val/test x class subdirectories, so loose files would in fact
be legal. Parquet is still right -- HF requires well-integrated formats for large
datasets, it is what makes the viewer work, and 27,710 loose files would eat a quarter of
the <100k recommendation for nothing -- but it is a strong recommendation here, not a
wall, and the doc now says which.
Records the consideration that IS real, and it is not file sizes: projectsidewalk is a
free organisation, and free orgs get "best-effort" public storage. The existing 463 GB
rests on a human approval from July 2025 rather than an entitlement, and HF states they
run mitigations against abuse of free public storage. Our +29 GB is +6% and squarely the
kind of artifact that approval was for, but the arrangement is informal, so a courtesy
note on the existing thread belongs in the publish steps.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jonfroehlich
force-pushed
the
feat/publish-stage1-inputs
branch
from
August 4, 2026 17:44
a4373c1 to
799e3e5
Compare
Two of the three planned repos are live, both byte-verified end to end. https://huggingface.co/projectsidewalk/rampnet-crop-model 720.7 MB https://huggingface.co/datasets/projectsidewalk/rampnet-stage1-inputs 1.03 GB The crop model was the binding blocker: inference_isolator.py loads the round-2 checkpoint by a hardcoded path, so until now the committed government inventories were inputs to a pipeline nobody outside the lab could execute. Both rounds are up, since round 1 initialises round 2. rampnet-stage1-inputs carries location_data/ (mirroring the committed copy), street_data/ (the full 801 MB downloads behind the committed 18.7 MB derivative), and manifests/ -- which is the part that actually matters. generate_negative_panos.py samples with an unseeded RNG, so negativepanosSHORTENED.jsonl is the ONLY record of which 43,834 negatives the paper used; they cannot be regenerated, only downloaded. Adds scripts/export_stage1_inputs.py and its card template, alongside the crop-model exporter. It asserts location_data sha256 against the values pinned in docs/data_provenance.md before staging anything, so the exporter cannot publish inventories that drifted from the ones the paper ran on. Verification, rather than trusting the upload: HF stores each LFS object under its sha256, so the oids were compared directly against the local hashes -- 8/8 match. The three files small enough to land as plain git blobs were re-downloaded and hashed instead. All 11 files confirmed identical to what was staged. Both cards state the limits beside the data: unseeded shuffle, unseeded negative sampler, and the convert_date change that affects 8.36% of records. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…twice
HF asked for Parquet or WebDataset rather than loose image folders when they approved
hosting rampnet-dataset in July 2025. Loose per-city folders would have been legal (125
entries against a 10k limit) but it is the shape they steered us away from, the viewer
would not work, and restructuring later means replacing large blobs -- the one expensive
operation on HF. So: done properly the first time.
scripts/export_benchmark.py streams each split into one Parquet per (config, city) with
pyarrow, which keeps memory flat across 11.41 GB instead of materialising 2 GB splits.
Three configs, named by resolution rather than by consumer:
native 1,109 panoramas as fetched, 4096-16384 px 9.99 GB
4096x2048 the same at the model input size 1.00 GB
galleries 314 incremental-FP crops from the #55 A/B 0.40 GB
The image bytes are embedded verbatim -- rows carry image as {bytes, path}, so nothing is
re-encoded on write -- and each row also carries the sha256 of its own bytes. Confirmed
that datasets reads the column back as a real Image feature, which is what makes the
viewer render rather than showing an opaque struct.
Two DIFFERENT things are verified, because they are different claims:
verify re-hashes every embedded image straight out of the Parquet.
2,532/2,532 round-trip byte-identical. Proves Parquet preserved whatever
we put in.
cross-check compares the packaged native bytes against the sha256 pinned in each
split's committed imagery_manifest.json, written at review time.
1,109/1,109 match. Proves what we put in is what the ground-truth
reviewers actually judged -- the claim that matters to a second rater,
and one the first check cannot make.
Labels stay in git on purpose. records.jsonl and verdicts.json get revised; imagery does
not, which is what makes this repo purely additive and safe to grow one city at a time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
https://huggingface.co/datasets/projectsidewalk/rampnet-benchmark 11.41 GB 27 parquet files uploaded and LFS-oid-verified against local hashes. Then the check that actually matters, from a clean client: load_dataset() against the Hub returns all three configs and nine splits, decodes the image column as a real PIL image, and the sha256 of the bytes that came back off the Hub matches the value recorded in the row -- which in turn matched each split committed imagery_manifest.json. The chain from a reviewer eyes to a stranger download is now closed and checkable at every hop. Updates the ledger, including three claims that were true this morning and are not now: - "benchmark/*/panos/ -- 9.0 GB, git-ignored, HF #21 pending, BLOCKER" -> published. - "Crop-model checkpoints -- lab storage only, BLOCKER" -> published. - The #46 run-book no longer opens by naming two blockers; everything it needs is obtainable. And the part worth stating plainly: ALL THREE human review passes are now redoable by someone outside the lab. Two of them (GT verification, the #55 A/B) previously needed imagery that existed only on lab machines. The judgments and rubrics were committed all along; publishing the pixels is what closed the loop. In particular the second-rater pass on Budapest -- whose ground truth was reviewed at LOW confidence and which is the top outstanding follow-up in the model comparison -- is blocked on a PERSON now, not on an artifact. Remaining: the 15 GB round-1 Project Sidewalk crop set, planned for rampnet-crop-model-dataset/round1_ps/ as Parquet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…step Creating a HF repo does NOT add it to a collection -- that is a separate API call, and none of the exporters make it. All three repos published today were reachable only by direct URL or by scrolling the org listing, which is precisely the discoverability gap publishing was supposed to close. Caught by Jon looking at the collection page. The RampNet collection now holds six items: rampnet-model, rampnet-dataset, rampnet-crop-model-dataset, and the three added today. Documents the step in docs/replication.md with the exact call, including the detail that cost a minute: the collection slug carries an id suffix (projectsidewalk/rampnet-6871b77b1add07bdfecfcd5c), so the bare .../collections/ projectsidewalk/rampnet URL does not resolve through the API. list_collections finds it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s visible The 9-city benchmark is NOT part of the RampNet paper, and nothing said so. The card I published yesterday was actively misleading -- it described the panoramas as "from RampNet: A Two-Stage Pipeline... (ICCV 25)", which reads as the paper own evaluation set. benchmark/README.md never mentioned it either. The boundary is cleanly provable, not a judgement call: v1.0-iccv2025 (2025-08-15) contains no benchmark/ directory at all. Its tree is manual_labels, stage_one, stage_two and support files. The splits were added 2026-07-22 through 2026-07-31, eleven months later. The paper evaluated on the 1,000-panorama gold set in manual_labels/, sampled from its own three training cities; the benchmark exists to measure the published model OUT of domain -- non-US cities, Mapillary as well as GSV -- and against VLM detectors. So numbers from the benchmark are not comparable with the published ones: different ground truth, different cities, different matching protocol. Corrected in four places, three of them public: - the rampnet-benchmark card, now opening with a warning block (pushed; card-only update, no blob churn) - the HF collection description and the per-item note - benchmark/README.md - the README Published Artifacts table, which now marks the benchmark row and states that every other row is paper-era Adds a `card` mode to export_benchmark.py so a card can be re-rendered and pushed on its own. Rebuilding 11.41 GB of Parquet to fix a sentence would rewrite every blob, which is the one expensive operation on HF -- and cards get revised far more often than data does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tually asked for I had shipped pixels and left labels in git, which was a deviation from #21 rather than a fulfilment of it. The issue title is "Publish deployment validation GROUND-TRUTH as a HuggingFace dataset", and it also specifies per-pano source, capture date, camera heading and per-record source attribution. None of that was in the dataset. Adds a fourth config, `records`: one row per reviewed panorama with source, capture_date, lat, lng, camera_heading, width, height, copyright, detections[{x_normalized, y_normalized, confidence, verdict}], missed[{x_normalized, y_normalized, unsure}], no_missed, model_id, model_training_date, label_type 1,109 panoramas, 2,061 detections, 1,191 reviewer-marked missed ramps -- in 179 KB. The dataset is now self-contained: a stranger can score a model against this benchmark without cloning the repo, and `source` makes the cross-domain slice the one-line filter #21 asked for. verdicts.json stores judgments as a bool/string mix; normalised to correct / incorrect / unsure / duplicate, with the mapping documented on the card so nothing is lost. Cross-checked against the source BEFORE pushing: every pano, every detection index, coordinate equality to 1e-12, and missed-ramp counts. 1,109 panos, verdict distribution 1833/96/116/16 matching a direct survey of the committed files, 0 discrepancies. Index alignment between detections and dets[] is the thing that could have been silently wrong, so it is checked explicitly rather than assumed. Adds `records` and `card` modes that touch only what they change. A verdict correction now costs ~180 KB of upload instead of replacing 11.41 GB of image blobs -- and verdicts do get revised, which is why labels are a separate config rather than extra columns on the imagery. Labels remain DERIVED: benchmark/<city>/{records.jsonl,verdicts.json} stay the source of truth and the config regenerates from them. Rubrics, reviewer confidence and review notes stay in git, and the card says to read them before quoting a number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ce anchor The crop checkpoints went up as raw torch.save pickle archives -- they literally begin PK\x03\x04 -- while projectsidewalk/rampnet-model ships model.safetensors, because converting it was a deliberate item on the July re-publish. Publishing two 360 MB pickles into the same org, linked from the same collection, was inconsistent with a decision the project had already made. Calibrating the risk honestly: torch 2.6 defaults torch.load to weights_only=True, so this was not a live exploit path for most users. The reasons to fix it anyway are that HF runs a pickle scanner over model repos (scansDone was still false on ours), and that the inconsistency is indefensible if anyone looks. The fix is purely ADDITIVE, which is why it was worth doing to an already-published repo: the .pth files stay exactly where they are with their sha256 unchanged, nothing is orphaned, no link breaks. The "already up there, live with it" argument applies to REPLACING blobs, not to adding a file. Both rounds converted, 380 tensors each, with every tensor compared for dtype and exact equality after the round trip -- the converter refuses to write on any mismatch. The .pth is kept deliberately: its hash is what ties this artifact to the paper's run, and it is what inference_isolator.py loads unmodified. The card now leads with safetensors and says plainly why both formats are there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…made a real column The round-1 Project Sidewalk crop set (27,704 crops, ~15 GB) is the last unpublished paper-era input. Parquet because HF asks for it on large datasets and it is what makes the viewer work -- not because loose files would be illegal. The 10k limit is per FOLDER, and train/val/test subdirectories would have kept loose JPEGs legal; I said otherwise earlier and the docstring now records the correction. The interesting part is that the labels were hiding in the filenames. `007mz25c_-_118_596_-_478_611.jpg` is panorama 007mz25c with keypoints (118, 596) and (478, 611), parsed at load time by train.py. This export makes them a `keypoints` column, so the dataset is usable without reimplementing a filename parser. Coordinates are stored VERBATIM in the stored crop pixel space (683x2048) and deliberately NOT normalised. The loader multiplies them by exactly 0.5 while the image is resized 683 -> 352 on x, a factor of 0.515. That ~2% discrepancy is in the original code; helpfully normalising here would silently commit to one reading of it. The card says so and points at train.py. Two things that cannot be regenerated, both documented beside the data rather than as instructions someone would follow and get different results from: - download_data.py reads live from Project Sidewalk servers with no snapshot pinning, and those databases keep growing, so re-running builds a DIFFERENT training set. - splititup.sh makes the 70/15/15 partition with `shuf` and no seed, so even the split is not reproducible. (That is now the fourth unseeded step found in this pipeline, after the all_locations shuffle, the negative sampler, and the dataset split.) Ships as its own repo rather than a folder inside rampnet-crop-model-dataset: that repo serves its 1,214 loose JPEGs through imagefolder auto-detection, and adding an explicit configs block risks breaking a published viewer with 22 likes to gain tidiness. Both land in the RampNet collection, so discoverability does not depend on them sharing a repo. Train shards at ~1.5 GB so no single parquet is awkward to resume. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… instead Building the export surfaced a wrong claim in a card I wrote before seeing the data. `n_keypoints` was documented as "0 is possible -- negative crops". There are none. All 27,704 crops carry at least one keypoint, 35,757 between them: 21,751 have exactly one and the tail runs to nine. A model trained on this set alone never sees an empty example, which is exactly the sort of thing you want to know before you use it. So the card no longer asserts a distribution -- it renders one, reading n_keypoints back out of the built shards. If a future export does contain negatives it will say so, with the count. It cannot drift from the data it ships with. write_card is split out of build and exposed as a `card` mode, because correcting one sentence should not mean rebuilding 13 GB of Parquet. The tests exist because parse_name is the entire value-add of this export and nothing else checked it. The byte-level verify pass is not a guard here: it re-hashes image bytes, so it stays green even if every keypoint were silently dropped. Eight tests cover multi-keypoint names, verbatim negative coordinates, the no-keypoint case, a segment that is not a coordinate pair, and both branches of the summary. pyarrow is declared in requirements-dev.txt rather than borrowed from datasets. What the build measured, for the record: - the 13,389,588,480-byte archive hashes 7fd446c1c9fd...8ec9 on klone and identically after transfer, so the crops that went into Parquet are the rescued paper-era files unchanged - 27,704 crops in, 27,704 rows out, split 19,392 / 4,155 / 4,157, 11 shards, 13.37 GB - every crop re-hashed against its stored sha256: all round-trip byte-identical - an independent re-parse of the filenames, deliberately not importing the exporter, agrees exactly -- 35,757 keypoints, zero unparseable segments, x in [0, 682] and y in [256, 1612] One correction to 0080473's message: rampnet-crop-model-dataset has 22 DOWNLOADS, not 22 likes; it has one like. The reason to leave that repo alone still holds -- its viewer works off imagefolder auto-detection and an added configs block would risk it -- but the argument is "do not break something that works", not "it is popular". The docstring said ~15 GB from an estimate made before the tar existed. It is 13.4 GB. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The plan in replication.md §4 -- put the round-1 crop set into rampnet-crop-model-dataset under round1_ps/, leaving the 1,212 round-2 JPEGs loose -- does not work. Worse, it does not work SILENTLY. I dry-ran it against throwaway repos before touching the published one, which is the only reason this is a commit rather than a broken dataset. `datasets` infers ONE builder module per repository, from the default config, and applies it to every config in that repo: - round 2 default: both configs get Imagefolder. The round-1 Parquet patterns resolve correctly, the shards are found, and then the imagefolder builder looks inside them for images, finds none, and yields 0 rows. No error until you ask for a split, at which point it is `Instruction "train" corresponds to no data!` - round 1 default: both configs get Parquet, and the JPEG config breaks instead. There is no arrangement that works. A parquet-only control with the identical config block loads fine, which is what isolates the cause to the mixing rather than the pattern. scripts/analysis/hf_config_mixing_check.py reproduces all three against throwaway repos it deletes afterwards. Unifying would mean converting the 1,212 round-2 JPEGs and deleting the originals -- replacing a published layout, not adding to one. b569c59 already drew that line for this project, and it still holds. The cost is a real naming wart, since the unmarked repo is round 2 and the marked one is round 1; the mitigation is the two-row table both cards carry, which is additive and breaks nothing. Two label problems the build surfaced, both of which would have shipped: - crop_uid, not pano_id. I had published the leading filename token as a panorama id. download_data.py:277 builds it with random.choices(alphabet, k=8) -- it is opaque, and the source panorama is not in this artifact at all. The measurement that caught it: 27,704 distinct tokens for 27,704 crops, where a real pano id would repeat (the paper's own table says these crops come from 20,698 panoramas). Naming it pano_id would have invited a join against rampnet-dataset that silently returns nothing. A test now asserts the schema has no pano_id field. - The x-axis mismatch is 3.1%, not the ~2% I wrote, and it is x-only. train.py scales both keypoint axes by 0.5, but Resize((1024, 352)) scales the image by 0.5 on y and 352/683 = 0.5154 on x. So y is exactly consistent and x is under-scaled, drifting a label left of its ramp in proportion to x, up to ~10.5 px at the right edge. The card now shows the per-axis table and tells you to scale x by 352/683 if you are not reproducing the paper. Rebuilt on the corrected schema and re-verified: 27,704 crops, 11 shards, 13.37 GB, every crop round-tripping byte-identical, 35,757 keypoints. Also measured while checking the card's other claims: all 27,704 crops are exactly 683x2048, so that claim was right. 510 tests pass. Co-Authored-By: Claude Opus 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.
v1.0-iccv2025shipped the Stage 1 code but not the data it consumes. This publishes what can goin git, proves what the published files became, and writes down where the rest goes.
The gap this closes
The three open-government curb ramp inventories the paper ran on were excluded from git by a
location_data/*line instage_one/dataset_generation/.gitignore. That line is how the omissionhappened, and it is not a gap re-downloading can close: the city portals serve current data
and it drifts (Bend +8.7%), so a download made today reproduces a different experiment.
All three are now committed (71.8 MB) and hash-pinned in
docs/data_provenance.md§3, with a new.gitattributesmarking thembinaryso no contributor'score.autocrlfcan silently invalidatethose hashes.
Which government records became training labels
combine_location_data.pyreduces the inventories to(latitude, longitude, date)and shuffles,discarding NYC's
RampID/CornerID, the geojsonOBJECTID/FacilityID/NonAssetID, and eventhe city label — so
all_locations.csvcannot answer "is this ramp in the training set?".scripts/analysis/gov_provenance.pyrebuilds the mapping and verifies it: 276,071 / 276,071 rowsresolved, 0 unmatched.
43.23% of the government records never became a training label. That belongs next to any claim
about how much open-government data this pipeline converts.
Three findings that constrain any Stage 1 re-run
random.seed(42)was added later, so the published roworder is unrecoverable. My first implementation replayed the seeded shuffle; verification
caught it on row 0, and the working method is a coordinate join instead. This is the Stage 1
counterpart of the split-seeding caveat in
data_provenance.md§4.convert_datechanged after the paper. Unknown install dates were"2000-01-01", now"".Measured: 276,066/276,071 dates reproduce under paper-era logic vs 252,983/276,071 (91.64%)
under current
main. The 23,088-row gap is exactly the records with no install date —8.36% of the corpus, every one silently admitted to the installed-before-captured check.
whole of the residual date disagreement.
Street centrelines: 801.6 MB → 18.7 MB, proven equivalent
street_data/could never live in git —New York - Streets.geojsonalone is 669 MB. But its onlyconsumer,
generate_negative_panos.py, reads the geometry plus one name field used solely asan emptiness test. Route numbers, ZIP, MSAG, ESN, road class and county are never touched.
scripts/build_street_derivative.pystrips the rest: 42.9× smaller, New York alone 67.5×. Thename field is kept rather than dropped because Portland has 4,192 features with an empty
FULL_NAMEthatload_city_streetsskips — a pure-geometry file would silently re-admit them.Equivalence is proved, not asserted.
verifycomputes a consumer fingerprint — sha256 over theordered (name, geometry) pairs of every feature surviving the filter, which is exactly what the
length-weighted sampling index is built from. All three match, and reproduce identically on Windows
and on the cluster.
generate_negative_panos.pyprefers a full download when present, so existingcheckouts are unaffected.
The Hugging Face plan
docs/replication.mdnow carries the full plan andREADME.mda "Published Artifacts" table, sono input is discoverable only by asking us. Ordering is driven by what blocks what, and the top
item was not the obvious one:
rampnet-crop-model(720.7 MB) first.inference_isolator.pyhardcodes a path to theround-2 crop checkpoint — the model that turns every government coordinate into a pixel keypoint
— and it exists only on lab storage. Publishing
location_data/without it hands someone theinputs to a pipeline they still cannot run.
rampnet-stage1-inputs(1.06 GB), manifests first.finaldataset.jsonlandnegativepanosSHORTENED.jsonlare the only way to reproduce the paper's negatives, because thesampler is unseeded.
rampnet-benchmark(~12 GB, Publish deployment validation ground-truth as a HuggingFace dataset (Bend GSV + Richmond Mapillary) #21) incrementally. Benchmark imagery is immutable once fetchedand the revisable parts live in git, so the set only grows and a new city costs one folder.
Folders named
panos_native/andpanos_4096x2048/— by resolution, not by consumer, so thename cannot rot when the model's input size changes.
Checked the two live repos against the July 2025 terms rather than assuming we had followed them:
rampnet-datasetis Parquet in 128 shards per split as advised;rampnet-crop-model-datasetis1,214 loose JPEGs, under the per-folder limit but not the recommended shape.
Also checked the whole plan against HF's current limits, and nothing is within an order of
magnitude of one: largest file 669 MB (limit 500 GB), largest folder 125 entries (limit 10,000),
largest repo ~2,550 files (recommended <100k). The per-city subdirectory layout is what keeps the
folder count trivial. The real consideration is the storage quota —
projectsidewalkis a freeorg, whose public storage is "best-effort", and the existing 463 GB rests on the July 2025 human
approval rather than an entitlement. Our +29 GB is +6%, but a courtesy note on that thread belongs
in the publish steps.
Published to Hugging Face
All three planned repos are live, in the RampNet collection:
rampnet-crop-modelrampnet-stage1-inputsrampnet-benchmarknative/4096x2048/galleries— the imagery half of #21Uploads were verified rather than trusted: HF stores each LFS object under its sha256, so oids were compared directly against local hashes (8/8, then 27/27). Files too small for LFS were re-downloaded and hashed. For the benchmark,
load_dataset()against the Hub returns bytes whose sha256 matches both the row and the committedimagery_manifest.json.All three human review passes are now redoable by someone outside the lab. GT verification and the #55 A/B previously needed imagery that existed only on lab machines. In particular the second-rater pass on Budapest — the top outstanding follow-up in the model comparison — is now blocked on a person, not an artifact.
Remaining: the 15 GB round-1 Project Sidewalk crop set, planned for
rampnet-crop-model-dataset/round1_ps/.Verification
pytest -q— 501 passed, at each commit.gov_provenance.pyre-run under both paper-era and currentconvert_date; both figures quotedabove come from that run.
build_street_derivative.py verify— 3/3 fingerprints match, on two platforms.🤖 Generated with Claude Code (claude-opus-5[1m])