Skip to content

fix: list batch summaries, file set paths and record set ids in a stable order - #154

Merged
rafiattrach merged 11 commits into
mainfrom
fix/stable-summary-order
Sep 30, 2026
Merged

rafiattrach merged 11 commits into
mainfrom
fix/stable-summary-order

Conversation

@renato-umeton

@renato-umeton renato-umeton commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

came up in review of #152: handler batches arrive in rglob order, which is whatever the fs gives us, so the same folder can bake differently on mac (APFS) vs linux (ext4). #152 tests use single value batches on purpose to dodge this, so this is the actual fix.

before, same 3 dicoms, two orders:
3 DICOM files (512x512): MR (1), CT (2)
3 DICOM files (512x512): CT (2), MR (1)
after, always CT (2), MR (1).

sorted per handler, same as image_handler does for format_counts (#140 also gives wsi flavors a fixed order). sorting discovery itself would renumber every FileObject id and reshuffle all goldens, and the e2e tests already compare via _discovery_independent, so discovery stays as is.

fixed:

  • dicom modality counts sorted, files w/ no modality pinned last as "unknown"
  • nifti dtypes sorted
  • fhir chunk + bundle FileSet includes sorted by path
  • the stored forms of a listed path (x.ndjson + x.ndjson.gz) and linked dupes riding w/ a FileSet were in rglob order, now sorted by path
  • record set ids: lab results.csv + lab@results.csv both sanitize to lab_results and the __2 went to whichever came first. make_record_set_ids (csv, json, fhir) and wfdb now allocate in path order like allocate_record_set_ids already does

already stable: image/OME, parquet, soft/spreadsheet/hdf5 ids, dataset description, references, scan report, duplicate primary pick. FileObject ids and record set order still follow discovery on purpose.

also 9dd8250 + 2ba97dc: dicom counted files w/ no Modality under the key "unknown", so a file whose Modality literally reads "unknown" got its count overwritten by the missing ones (3 files, counts summed to 2). missing ones now sit under None, still last, counts add up to num_files
they still print as "unknown" so normal batches read the same, no golden changes. only if some file says unknown (any case) do the missing ones print as "no modality", e.g. "CT (1), unknown (1), no modality (1)"

ties in the fhir type vote are also order dependent, left for a follow up PR.

each fix has a test that reverses the batch or discovery and fails on main. no golden changed. merges clean w/ #152, #140, #148.

@slobentanzer pls take a look when u get a chance.. thx!

Both summaries kept their counts in insertion order, which is rglob order,
so one mixed directory got a different description on macOS and Linux.
Sort them by name, the way the image handler sorts its formats.
The chunk and bundle FileSets listed their files in batch order, which
is rglob order. Sort them by path like the image and OME FileSets.
A FileSet path held in several stored forms, and the duplicates riding
with its files, were listed in rglob order. Sort both by path.
make_record_set_ids and the WFDB handler settled a collision their parents
could not separate with __2 in batch order, which is rglob order, so
lab results.csv and lab@results.csv swapped ids between filesystems.
Allocate in path order the way allocate_record_set_ids already does.
Files with no Modality were counted under the key "unknown". A file
whose Modality actually reads "unknown" (invalid CS, but read as is)
landed on the same key, and the missing count then overwrote its
count, so the modality counts no longer added up to num_files.

Missing ones now go under None, sorted last and rendered as "unknown"
as before, so descriptions of normal batches are unchanged.
After 9dd8250 a batch with a file whose Modality reads "unknown" and
a file with none printed "unknown (1), unknown (1)". When any counted
modality equals "unknown" in any case, files with none now print as
"no modality". Other batches still print "unknown", so no golden
changes.

The tests also check that the counts add up to num_files, and the
code comment now says missing files are counted apart under None.
slobentanzer added a commit that referenced this pull request Sep 29, 2026
mlcroissant 1.1.0 raises a GenerationError when reading these record
sets, as the OME note already says. Also drop two test comments about
discovery order that stop being true once #154 sorts the counts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@slobentanzer slobentanzer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @renato-umeton,
good catch, I just recommend minor efficiency changes:

  1. Duplicated code in utils.py. The new disambiguate_in_path_order repeats the four lines at the end of allocate_record_set_ids (the order = sorted(...) block at utils.py:229-232). allocate_record_set_ids should call the new helper, so path-order allocation lives in one place.
  2. Pick one label for DICOM files with no Modality. Right now those files print as "unknown", or as "no modality" if another file in the batch says "unknown". So the label depends on the other files. The PR keeps "unknown" so that goldens stay the same, but no golden or existing test contains "unknown (". Could instead (simpler): always print "no modality", remove the clash check, and remove the uppercase UNKNOWN test.
  3. Repeated test setup. The same monkeypatch that reverses scan.discover_files appears five times in four test files. It should be one fixture in tests/helpers.py or conftest.py.
  4. Local import in test_nifti_handler.py. test_a_mixed_dtype_bake_describes_itself_one_way imports bake inside the function, with a comment saying this avoids a merge conflict with #152. Once both are merged it should be a top-level import and the comment should go. Follow-up issue?

@renato-umeton

Copy link
Copy Markdown
Collaborator Author

thx @slobentanzer, all good points, done:

  1. allocate_record_set_ids now gets its bases from disambiguate_in_path_order, so the bases come from one place. the derived ids loop still walks path order (1 line, needed for __2). same ids, goldens untouched 28347af
  2. files w/ no Modality always print "no modality". clash check + UNKNOWN test gone; None key kept so a real "unknown" still counts on its own and counts add up. checked, no test or golden here or in feat: digital pathology whole-slide image formats (SVS, NDPI, SCN, BIF, QPTIFF, DICOM WSI) #140 test: raise line coverage of croissant_baker to 96% #148 fix: drop source.extract from dicom and nifti header fields #152 test: refresh the rai golden and compare it in full #155 has "unknown (" e9d87a5
  3. one reverse_discovery fixture in tests/conftest.py (where dataset lives), used by the 5 copies 4231a58 and by the ones already on main (golden, ome, spreadsheet) d0b81fd, w/ readable ids (as-walked / reversed) 152feae
  4. opened tests: top level bake import in test_nifti_handler + last reversed discovery copies #159 for 4, plus the 2 copies still coming in w/ feat: digital pathology whole-slide image formats (SVS, NDPI, SCN, BIF, QPTIFF, DICOM WSI) #140 and test: refresh the rai golden and compare it in full #155

trial merged w/ #140 #148 #152 #155, all clean + green. thx!

rafiattrach pushed a commit that referenced this pull request Sep 29, 2026
* fix: drop source.extract from dicom and nifti header fields

A content extract selects the whole file. Nothing in it names the tag or
header slot a field describes. Header fields now name their FileSet and
nothing else, the shape the OME-TIFF fields already use. Types,
descriptions and ids are unchanged. The SPECT golden loses only the
extract blocks.

Refs #124

* docs: say dicom and nifti header fields need a format reader

Refs #124

* docs: say mlcroissant cannot read dicom and nifti header records

records() raises at the read step on these RecordSets.

* docs: say records() fails on dicom and nifti header record sets

mlcroissant 1.1.0 raises a GenerationError when reading these record
sets, as the OME note already says. Also drop two test comments about
discovery order that stop being true once #154 sorts the counts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Sebastian Lobentanzer <sebastian.lobentanzer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@slobentanzer slobentanzer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@renato-umeton looks good, thanks :)

@rafiattrach
rafiattrach merged commit f5b67d9 into main Sep 30, 2026
3 checks passed
renato-umeton added a commit to renato-umeton/croissant-maker that referenced this pull request Oct 1, 2026
Brings in MIT-LCP#127 (VCF, BCF, BAM, CRAM, SAM, FASTQ and FASTA handlers),
MIT-LCP#154 (stable summary, file set and record set order) and the 0.7.0
release.

Conflicts:
- README.md: main's WFDB and Images rows, then this branch's
  whole-slide row and its DICOM row with the whole-slide fields.
- handlers/utils.py: union of the typing imports (Iterable from this
  branch, BinaryIO and Iterator from main).
- tests/helpers.py: union of the imports (lzma, struct, zlib, Fraction)
  and of the __all__ names (the whole-slide samples and builders, plus
  VCF_HEADER_TEXT, bake_validated, bcf_payload, cram_payload, cut_gzip).

docs/_generated is regenerated with docs/generate.py and matches the
merged file. The whole-slide golden is unchanged: the golden test
passes in both discovery orders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants