NDOS 0.2.0 — tooling for animal-lab data that was never organised to any standard

Most data standards assume you are starting a project. This is for the other
case: a drive arrives, the person who filled it has graduated, and three years
of recordings are in there with nobody left who can say which animal is which.

NDOS reads that drive without changing it, works out what is in it, and lets
you ask questions of the result. It is Python 3.9+ with no third-party
runtime dependencies
, because the machine that most needs this is often an
acquisition PC where nothing can be installed.

pip install ndos
ndos report /path/to/the/drive

Where it sits relative to BIDS and NWB — the first question anyone asks

It does not compete with either, and it does not claim conformance it cannot
demonstrate.

ndos convert bids produces a BIDS-shaped export, and says so in exactly
those words:

BIDS-shaped, not validated BIDS. Animal electrophysiology is covered by
BEP032, which is not finalised, so no export can claim conformance today.

It maps to ephys, beh and micr. ndos convert nwb emits a conversion
plan with metadata mapped onto NWB’s fields, for NeuroConv or a lab script
to execute — NDOS does not reimplement NWB writing, which is solved and
maintained elsewhere.

There is a directory specification in the repository, but it is one component
and deliberately separable. If your lab already uses
NeuroBlueprint or has its own
convention, the inventory, search, metadata and provenance parts still work on
what you have. The specification names three levels for this reason:
readable (works on whatever organisation exists), conformant, and
traceable.

What 0.2.0 adds

Search that leads somewhere. You remember a word — CA1, a construct
name — and the thing that knows where it applies is a surgery log nobody has
opened in three years.

ndos search index /path/to/drive
ndos search find "CA1 AND injection"

It reads filenames, notes, protocols and spreadsheets, including .docx and
.xlsx (ZIPs of XML, so no parser dependency; PDFs are not read and it says
so). A hit in that surgery log reports which animals the text names, and
where those animals’ recordings are
— so a word leads to the data, not just
to a document.

Checking a transfer arrived intact. NDOS does not move data; rsync,
rclone, DataLad and datashuttle do that well. What none of them reports is
whether what arrived is still readable.

ndos scan /source -o before.json
# ... transfer however you already do ...
ndos verify before.json /destination

Three results rather than two — VERIFIED, FAILED, and INCONCLUSIVE for
when no mismatch was found and nothing was proven either (a manifest with no
checksums, a path neither end could read). A Kilosort sorting that arrived one
file short is reported as an unopenable sorting rather than as one missing
path, because that is what it is. A .bin that arrived without its .meta is
flagged for the same reason.

The part I would most like argued with

Cohort queries return three answers, not two: matched, excluded, and cannot
be ruled out
.

A session whose species was never recorded is not a session known not to be a
mouse. Collapsing those two cases gives you a cohort quietly biased toward
whichever animals happened to have fuller records, and nothing in the output
says so. A worked example is in the repository
(examples/cohort-demo),
with committed output: four animals, one query, one match, one excluded, and
two undecidable for two different reasons — one checked and unknown, one
never entered.

I would genuinely like to hear from anyone who thinks this is the wrong model,
or who has a case it handles badly.

Status, plainly

489 tests on Python 3.9 and 3.13 across Linux, macOS and Windows, including a
CI job that runs every module against a bare interpreter so the
zero-dependency property cannot lapse quietly.

No lab outside my own has completed a pilot. That is what I am looking
for. It takes about fifteen minutes and will not move or change your data, and
the single most useful thing anyone could send me is a folder layout it
reads wrongly
— the inference rules have met too few real conventions to be
trusted yet.

The worked example makes the unknown-versus-missing distinction clear. What happens when two records disagree, say a filename and a surgery log naming different animals? Can the report flag that conflict rather than choosing one automatically?

1 Like

Thank you for catching this!
It could not, and it resolved the conflict silently and nothing in the output said a conflict had happened.

Concretely: a file A3302-190809.dat sitting inside a folder M456/. Two sources name a subject and they disagree. The plan said:

why   filename 'A3302-190809.dat' names the animal and the date
→ raw_data/A3302/20190809/...

Which is true, and it never mentioned that the folder had claimed M456 and been overruled. A resolved disagreement read exactly like a clean read, which is the one thing this project is supposed to not do.

Fixed now. The plan reports it per placement:

!     folder says 'M456', filename says 'A3302' — took 'A3302'
      because the filename is more specific than the folders around it: a
      session archived as one file usually sits under its cohort, so trusting
      the folder would label every animal with its group

and collects them, so you do not have to read every line to find them:

SOURCES THAT DISAGREED (1)
  Two sources named a subject and named different ones. A rule chose, and
  these are the files where it had to. Worth reading if the rule chose
  wrongly for your layout.

  raw_data/A3302/20190809/A3302_20190809_a3302-190809.dat
      filename: 'A3302'   (rejected folder: 'M456')

I deliberately did not change which source wins. The precedence has a real reason behind it, a session archived as one file usually sits under its cohort directory, so trusting the folder would label every animal with its group, and “the folder always wins” would be wrong in somebody’s lab.
Choosing a different rule is not the fix for failing to announce the old one.

Two parts of what you raised are still open, and I would rather say so than imply it is done. ndos table check reports Problems: 0 when a declared subject_id contradicts the observed folder, it catches the broken link to animals.csv that results, but not the contradiction that caused it. And
search already puts both claims side by side for your surgery-log case:

raw_data/M456/20250314/surgery_notes.txt
    Surgery log. Injected M123 in dorsal [CA1] on this date.
    subject M456 · session 20250314 · names M123
    → M123 has no sessions recorded

which is close to right, the “no sessions recorded” line is usually the interesting part, but it is still only displaying the disagreement rather than naming it. Both are tracked in the issue.

Issue: Two records that disagree are resolved silently, and organize overwrites the losing claim · Issue #35 · Elnazkarami/N-DOS · GitHub
Fix: Say when two sources named different subjects (#35) by Elnazkarami · Pull Request #36 · Elnazkarami/N-DOS · GitHub

Thank you for this! It is among the first outside reports the project has had, and it found something real in one question. And this is precisely why I ask for pilot tests!

If you have a case where two records disagree, even just its shape with no data, I would like it. The thing I most want to know is which source tends to be right when they fight in practice, because my instinct is that it varies by lab and that is an argument for never resolving silently rather than for
picking a better default.

The conflict surviving in the saved plan is the useful bit: someone can revisit the decision later. For the remaining table check, I’d test a case where both animal IDs exist in animals.csv. Then every link can be valid while the identity is still wrong.

indeed! I am calling for real test users with lab data, to find these gaps. I’d appreciate spreading the word for this.Thanks for your input!