Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
62904b6
fix: report the installed package version from mars.__version__
maccoss Aug 20, 2026
0599105
feat: add a C# implementation of MARS targeting .NET 8 and .NET 10
maccoss Aug 20, 2026
ce4bae2
fix: keep vendored Osprey.ML sources untranslated so the drift guard …
maccoss Aug 20, 2026
c2f8054
ci: build, test, package and release the .NET implementation
maccoss Aug 20, 2026
4c31850
docs: make the README .NET-first and add a docs folder
maccoss Aug 20, 2026
207a231
docs: add a release-notes process with separate Python and .NET tracks
maccoss Aug 20, 2026
4250f81
feat: add --dump-matches and the scripts to difference it against the…
maccoss Aug 20, 2026
279b4e7
test: verify the C# matcher against the Python one row by row, and fi…
maccoss Aug 20, 2026
b02bb7e
docs: record --dump-matches and the parity result in the .NET release…
maccoss Aug 20, 2026
deae72e
release: adopt CalVer v26.1.0 as the single release track and retire …
maccoss Aug 21, 2026
c683d1d
feat: write the QC figures as a single self-contained HTML report
maccoss Aug 21, 2026
7fd6c3b
docs: document the algorithms, the CLI, the model and how the code fi…
maccoss Aug 21, 2026
389a14e
feat: give mars qc the same figures, minus the ones that need a model
maccoss Aug 21, 2026
e43b3f1
docs: note mars qc figures in the release notes
maccoss Aug 21, 2026
4a3b04b
feat: cross-validate by peptide, and make the reported accuracy the h…
maccoss Aug 21, 2026
517de26
feat: show cross-validation variance in the reports, and verify deter…
maccoss Aug 21, 2026
04cbcd7
feat: merge cross-validation folds into one model, exactly
maccoss Aug 21, 2026
23283f8
feat: merge the folds into one model and remove refit
maccoss Aug 21, 2026
310e04f
docs: correct the --cv-folds help text for the merged model
maccoss Aug 21, 2026
91f4f8e
feat: fit the correction model to all the data, and report both numbers
maccoss Aug 21, 2026
3ffe594
feat: refit without the rows the first pass could not explain
maccoss Aug 21, 2026
d46588c
feat: robust second pass, with trim as the default because Huber meas…
maccoss Aug 21, 2026
3d49694
test: verify the port on Orbitrap Astral data, and soften the Huber c…
maccoss Aug 21, 2026
896d130
feat: report ppm alongside Th, and rebuild the heatmap as a shared-sc…
maccoss Aug 21, 2026
cfee65a
feat: viridis density panels, readable labels, larger type
maccoss Aug 21, 2026
35514d3
feat: detect the mass analyzer and configure MARS for it
maccoss Aug 21, 2026
5c51a9b
fix: stop mars verify destroying its input, and collect only what qc …
maccoss Aug 21, 2026
bcf3f4f
docs: measure pwiz-sharp's Thermo reader against a real Astral raw
maccoss Aug 21, 2026
fed13cb
docs: pwiz writer reproduces the byte-splice output exactly
maccoss Aug 21, 2026
a2e9b56
feat: write mzXML, mzMLb and mgf through pwiz
maccoss Aug 21, 2026
92510a7
feat: read Thermo .raw directly, and write mzML or mzMLb from it
maccoss Aug 21, 2026
d6220cc
perf: parallelize the pwiz write, and pin the invariant culture
maccoss Aug 22, 2026
8810149
build: publish a self-contained executable that reads vendor RAW
maccoss Aug 22, 2026
46b42c7
feat: read Bruker and Sciex, and honour the scan-time unit
maccoss Aug 22, 2026
e2d8ed3
feat: use injection time as a feature only when it varies
maccoss Aug 22, 2026
4b0562f
docs: MARS sees timsTOF data as ~900 spectra per retention time
maccoss Aug 22, 2026
b90d0bc
feat: collapse the TIMS mobility dimension rather than modelling it
maccoss Aug 22, 2026
b8bbe8f
docs: bring the README, architecture and release notes up to date
maccoss Aug 22, 2026
0bcb605
test: pin the scan-time unit and the pwiz batching
maccoss Aug 22, 2026
8e13c2c
ci: pin pwiz-sharp, build vendor support in CI and in releases
maccoss Aug 22, 2026
ce2c83b
fix: register vendor readers from a static constructor, not a module …
maccoss Aug 22, 2026
1483211
docs: record that MARS ships the vendor SDKs, and when that changes
maccoss Aug 22, 2026
16fedea
docs: record how to find Skyline's install, verified against the one …
maccoss Aug 22, 2026
1ab25e2
docs: prefer Skyline-daily, but check whichever SDK is found
maccoss Aug 22, 2026
3d89a4c
docs: correct the Skyline discovery recipe against a real install
maccoss Aug 23, 2026
d781f9c
fix: --resolution was rejected as a typo, and warn on an over-wide to…
maccoss Aug 23, 2026
73fc2e5
feat: centroid profile spectra with the vendor's own algorithm
maccoss Aug 23, 2026
de1e286
fix: keep the ion-population features when the injection time looks flat
maccoss Aug 23, 2026
0a1d327
fix: act on the Copilot review, and a dead temperature warning it poi…
maccoss Aug 23, 2026
763eb2d
docs: consolidate the release notes into one v26.1.0 file
maccoss Aug 23, 2026
0e8b241
fix: path case, ppm array lengths, PNG dimensions, and a build comman…
maccoss Aug 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Vendored Osprey.ML sources are byte-for-byte copies of their upstream in pwiz, CRLF
# line endings included, and MARS.Test hashes them against UPSTREAM.json to turn an
# accidental local edit into a test failure. Git's autocrlf translation would make that
# hash platform-dependent - passing on Windows, failing on Linux CI - so these files must
# never be translated. Treat them as opaque bytes.
dotnet/third_party/** -text
246 changes: 246 additions & 0 deletions .github/workflows/dotnet-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
name: .NET Release

# Pushing a v* tag builds every platform artifact and creates the GitHub Release.
# Do not hand-create the Release: the notes file is published verbatim as the body, and
# this workflow is what guarantees the tag, the assembly version and the notes agree.
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to build, without the v prefix (e.g. 26.1.0). Builds artifacts only, creates no Release.'
required: true

permissions:
contents: write

jobs:
# Fail before spending twenty minutes on artifacts if the release is not consistent.
# Catching this after the builds have run is the failure mode the process doc warns about.
preflight:
name: Check version and notes
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
tag: ${{ steps.resolve.outputs.tag }}
steps:
- uses: actions/checkout@v4

- name: Resolve version
id: resolve
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
version="${{ github.event.inputs.version }}"
else
version="${GITHUB_REF_NAME#v}"
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "Releasing version $version"

- name: Assembly version must match the tag
run: |
version="${{ steps.resolve.outputs.version }}"
declared=$(grep -oPm1 '(?<=<Version>)[^<]+' dotnet/Directory.Build.props)
echo "tag says $version"
echo "csproj says $declared"
if [ "$declared" != "$version" ]; then
echo "::error::dotnet/Directory.Build.props declares <Version>$declared</Version> but the tag says $version. Bump it and re-tag."
exit 1
fi

- name: Release notes must exist
if: github.event_name != 'workflow_dispatch'
run: |
notes="release-notes/RELEASE_NOTES_${{ steps.resolve.outputs.tag }}.md"
if [ ! -f "$notes" ]; then
echo "::error::$notes is missing. Rename RELEASE_NOTES_next.md to it before tagging; it is published verbatim as the Release description."
exit 1
fi
# A renamed draft always arrives carrying the headings nobody filled in, and this
# file is published verbatim as the Release description.
empty=$(awk '
/^## / { if (heading != "" && count == 0) print heading; heading=$0; count=0; next }
NF { count++ }
END { if (heading != "" && count == 0) print heading }' "$notes")
if [ -n "$empty" ]; then
echo "::error::$notes has section headings with nothing under them:"
echo "$empty"
echo "Delete them before tagging - this file is the Release description."
exit 1
fi
echo "Release notes look complete."

build:
name: ${{ matrix.rid }}
needs: preflight
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Each artifact is built on its own platform where a runner exists, so the
# smoke test below exercises the binary that actually ships rather than a
# cross-compiled stand-in.
- { os: windows-latest, rid: win-x64, archive: zip }
- { os: ubuntu-latest, rid: linux-x64, archive: tar }
- { os: macos-latest, rid: osx-arm64, archive: tar }
- { os: macos-13, rid: osx-x64, archive: tar }
# No hosted arm64 runner in the standard pool for either of these, so they are
# cross-compiled and NOT smoke tested. GitHub's windows-11-arm runner can be
# swapped in for win-arm64 if it becomes available to this repository.
- { os: windows-latest, rid: win-arm64, archive: zip, cross: true }
- { os: ubuntu-latest, rid: linux-arm64, archive: tar, cross: true }

steps:
- uses: actions/checkout@v4

- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

# Released binaries carry the vendor readers, so a downloaded MARS opens a .raw with
# nothing else installed. That needs a pwiz-sharp checkout at the pinned commit, because
# it is an unmerged draft with no package feed.
- name: Read the pinned pwiz-sharp commit
id: pin
shell: bash
run: |
commit=$(python -c "import json;print(json.load(open('dotnet/pwiz-sharp.json'))['commit'])")
echo "commit=$commit" >> "$GITHUB_OUTPUT"

- name: Cache the pwiz checkout
id: pwiz-cache
uses: actions/cache@v4
with:
path: pwiz
key: pwiz-sharp-${{ runner.os }}-${{ steps.pin.outputs.commit }}

- name: Clone pwiz-sharp at the pin
if: steps.pwiz-cache.outputs.cache-hit != 'true'
shell: bash
run: |
# Blobless but a full working tree: Bruker reads archives from pwiz_aux and VC90 CRT
# files from pwiz_tools/Shared/Lib, and Common.csproj embeds .obo files from
# pwiz/data/common.
git clone --filter=blob:none --no-checkout https://github.com/ProteoWizard/pwiz.git pwiz
cd pwiz
git checkout --detach ${{ steps.pin.outputs.commit }}

- name: Pin the SDK for pwiz-sharp
shell: bash
run: |
cat > pwiz/pwiz-sharp/global.json <<'JSON'
{ "sdk": { "version": "8.0.0", "rollForward": "latestFeature" } }
JSON

- name: Publish
working-directory: dotnet
shell: bash
run: |
dotnet publish MARS/MARS.csproj -c Release -f net8.0 -r ${{ matrix.rid }} --self-contained true -p:PublishSingleFile=true -p:DebugType=none -p:PwizSharpDir="$GITHUB_WORKSPACE/pwiz/pwiz-sharp" -p:IAgreeToVendorLicenses=true -o publish/${{ matrix.rid }}

# Parquet.Net carries a native compression library, so the archive is a binary plus
# that library rather than a lone executable.
- name: List payload
working-directory: dotnet/publish/${{ matrix.rid }}
shell: bash
run: ls -la

- name: Smoke test
if: ${{ !matrix.cross }}
working-directory: dotnet/publish/${{ matrix.rid }}
shell: bash
run: |
if [ "${{ runner.os }}" = "Windows" ]; then bin=./mars.exe; else bin=./mars; chmod +x "$bin"; fi
"$bin" --version
# --version alone would pass on a binary whose dependencies are broken, because
# it never touches them. Run a command that opens a file and does real work.
"$bin" verify --help > /dev/null
"$bin" qc --help > /dev/null

# A build that silently missed pwiz-sharp compiles, runs and writes mzML, so it
# would pass everything above having shipped none of the vendor support. --version
# reports what this binary actually carries.
capabilities=$("$bin" --version)

# Only what every runtime identifier can do. Bruker and Sciex ship native x64
# libraries and mzMLb needs a native HDF5, so an arm64 artifact honestly reports
# fewer of them - osx-arm64 runs this check, and demanding .d here would fail a
# release that was working exactly as intended. Thermo's SDK is managed and mzXML is
# pure pwiz, so those two prove pwiz-sharp linked in on any target.
for expected in .raw mzXML; do
if ! echo "$capabilities" | grep -qF -- "$expected"; then
echo "::error::$expected missing from ${{ matrix.rid }} - vendor support did not build in"
exit 1
fi
done
echo "Smoke test passed on ${{ matrix.rid }}, with vendor support"

- name: Archive (tar.gz)
if: matrix.archive == 'tar'
working-directory: dotnet/publish/${{ matrix.rid }}
run: |
chmod +x mars
tar czf "../../../mars-${{ needs.preflight.outputs.version }}-${{ matrix.rid }}.tar.gz" .

- name: Archive (zip)
if: matrix.archive == 'zip'
working-directory: dotnet/publish/${{ matrix.rid }}
shell: pwsh
run: |
Compress-Archive -Path * -DestinationPath "$env:GITHUB_WORKSPACE/mars-${{ needs.preflight.outputs.version }}-${{ matrix.rid }}.zip"

- name: Checksum
shell: bash
run: |
cd "$GITHUB_WORKSPACE"
f=$(ls mars-${{ needs.preflight.outputs.version }}-${{ matrix.rid }}.*)
if command -v sha256sum > /dev/null; then sha256sum "$f" > "$f.sha256"; else shasum -a 256 "$f" > "$f.sha256"; fi
cat "$f.sha256"

- uses: actions/upload-artifact@v4
with:
name: mars-${{ matrix.rid }}
path: |
mars-*.tar.gz
mars-*.zip
mars-*.sha256
if-no-files-found: error

release:
name: Publish GitHub Release
needs: [preflight, build]
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

- name: Collect checksums
run: |
cd artifacts
ls -la
cat *.sha256 > SHA256SUMS.txt
rm -f *.sha256
cat SHA256SUMS.txt

# The notes file is the Release body, verbatim. Preflight has already confirmed it
# exists and carries no empty headings.
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: MARS ${{ needs.preflight.outputs.version }}
body_path: release-notes/RELEASE_NOTES_${{ needs.preflight.outputs.tag }}.md
files: |
artifacts/*.tar.gz
artifacts/*.zip
artifacts/SHA256SUMS.txt
fail_on_unmatched_files: true
Loading
Loading