Skip to content

[translate 2/3] Split the shared export machinery out of translate.py - #445

Draft
mo-sameh wants to merge 5 commits into
translate-refactor/1-characterizefrom
translate-refactor/2-restructure
Draft

[translate 2/3] Split the shared export machinery out of translate.py#445
mo-sameh wants to merge 5 commits into
translate-refactor/1-characterizefrom
translate-refactor/2-restructure

Conversation

@mo-sameh

@mo-sameh mo-sameh commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Goal

translate_diann.py imported five helpers from translate.py, which made the SWATH format the de facto shared library for the DIA-NN one. Move what both formats share into a new translate_core.py so neither depends on the other's dialect.

What's here

translate_core.py holds the modified-sequence rendering, the candidate precursor columns, and the fragment flattening. Each export keeps only its own column names and writer, and renames the canonical fragment columns into its own dialect through a dict.

The flattening is where the real change is. It used to filter by zeroing intensities in the caller's library and letting a later min_frag_intensity drop the rows. One design choice, four symptoms: a default export silently zeroed 60% of the library's intensities, a second export at a wider window returned different fragments than a fresh one, the export wrote a precursor_mz column into the caller's frame, and disabling the m/z window exported empty fragment slots at m/z 0. Fragments are now selected on a per-precursor copy and dropped rather than zeroed, which fixes all four.

Verification

Every export was run against main's modules over 19 configurations and compared including dtypes and index. The first four commits are byte-identical to main on all 19, with no test edits. The fifth differs on exactly the four cases that disable the m/z window — the fix — and the characterization tests that pinned the old behaviour change with it.

pytest warnings drop from 41 to 7; the SettingWithCopyWarning storm was the export writing into batch slices of the caller's library.

mo-sameh and others added 5 commits September 2, 2026 22:02
Clear the ground before restructuring, so the moves in the following commits
are readable. Output is unchanged.

- the commented-out `df.explode` fallback in `merge_precursor_fragment_df`,
  superseded by `explode_multiple_columns`, which handles the same old-pandas
  case it was guarding against
- two commented-out column assignments, `LabelModifiedSequence` and
  `ProteinGroups`
- `DiannParquetCols.SIGNATURE`, declared but never written; it is referenced
  only by the docstring saying DIA-NN wants it omitted, which stays
- the six `frag_*_head` arguments of `speclib_to_single_df`, which only passed
  their own defaults through to `merge_precursor_fragment_df`. No caller passes
  them, in alphabase, its notebooks, peptdeep or alphadia; the parameters on
  `merge_precursor_fragment_df` itself stay, since the DIA-NN export uses them.
- the pandas-version branch picking `to_csv`'s newline argument, duplicated in
  `WritingProcess.run` and `translate_to_tsv`, hoisted to one module constant.
  Kept rather than dropped: `requirements_loose.txt` does not pin pandas, so a
  pre-1.5 install is still possible.

Verified with a scratch harness comparing 15 outputs -- 7 transition-list
parameter combinations, 4 DIA-NN combinations, 2 tsv batchings and 2 parquet
batchings -- against the same functions loaded from main: all identical, frames
compared including dtypes and files by digest. Test files are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`merge_precursor_fragment_df` took the half-built *output* frame, so both formats
copied `frag_start_idx`/`frag_stop_idx` into it just for the flattener to read and
then dropped them again, and the DIA-NN export smuggled a precursor pointer
through it to flag base peaks.

Split it in two: `fragment_table` takes the index arrays and returns one row per
kept fragment in canonical columns plus `precursor_row`, and `join_fragments`
repeats each precursor row across its fragments. Each format now names the
fragment columns with a rename dict instead of `frag_*_head` arguments, so a
third dialect is a dict literal. Output is unchanged.

`fragment_table` still ends in `explode_multiple_columns` on purpose: building
the columns from typed arrays would change the exported dtypes, which belongs to
the fixes PR.

`merge_precursor_fragment_df` is removed; no caller exists in alphabase, peptdeep
or alphadia. `DIANN_PARQUET_FRAG_HEADS` is renamed to `DIANN_FRAGMENT_COLUMNS`,
and is in no released tag.

Verified with the scratch harness: 15 outputs identical to main, compared
including dtypes and index. Test files are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The m/z window zeroed intensities in the caller's library instead of dropping
fragments, so an export edited what it was handed, exporting twice at different
windows was order-dependent, and empty fragment slots (m/z 0) leaked into the
output when the window was disabled -- `translate_to_tsv` at 0/0 wrote nothing
else. Filter inside `fragment_table` on the per-precursor copy it already made,
and read precursor m/z through `get_precursor_mz` instead of writing it onto the
caller's frame. `mask_fragment_intensity_by_mz_` and
`mask_fragment_intensity_by_frag_nAA` go with the design they implemented.

An unbounded window is now expressed by its own bounds, 0 and `np.inf`, rather
than the 0/0 sentinel, which warns and is treated as unbounded.

Output is unchanged except where the window is disabled: of 18 outputs compared
against main, the four that differ are exactly those calls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mo-sameh mo-sameh changed the title translate refactor/2 restructure refactor: split the shared export machinery out of translate.py Sep 2, 2026
@mo-sameh mo-sameh changed the title refactor: split the shared export machinery out of translate.py [translate 2/3] Split the shared export machinery out of translate.py Sep 3, 2026
@mo-sameh
mo-sameh requested review from GeorgWa and mschwoer September 3, 2026 07:04
Comment on lines +9 to +10
Before this module, they lived in ``translate.py``, which made the SWATH format the de
facto shared library: ``translate_diann`` imported five helpers from it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please remove references to older code

return default


# @numba.njit #(cannot use numba for pd.Series)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

which argument is the series?

library's fragment frames are left exactly as they were. Fragments outside the m/z
window are dropped rather than zeroed, as are empty fragment slots -- a `*_modloss`
column of a precursor whose modification has no loss carries m/z 0, and selecting it
would export a fragment that does not exist. An unbounded window is expressed by the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please rephrase the last sentence

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

in general, docstrings are very LLM-ish, might be worth a human overhaul :-)

frag_masses = []
frag_intensities = []
frag_numbers = []
iters = zip(frag_start_idx, frag_stop_idx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

frag_start_stop_idx_iterator?

iters = tqdm.tqdm(iters)
for start, end in iters:
masses = fragment_mz_df.iloc[start:end, :].to_numpy()
keep = (masses > 0) & (masses >= min_frag_mz) & (masses <= max_frag_mz)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

keep_mask

]


def fragment_table( # noqa: PLR0913

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

get_fragment_table ?

frag_intensities.append(intens[idx_in_df])
frag_numbers.append(_get_frag_num(columns, rows, frag_len))

table = pd.DataFrame(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

fragments_df?

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.

2 participants