Skip to main content

msutils

Everyday Measurement Set operations for radio-astronomy pipelines — inspect, subset, average, manage columns and flags. No calibration, no imaging.

Academic attribution

Please acknowledge this project and its contributors when using the work in research, and cite the associated publications and software release where applicable. This is a scholarly request, not an additional licence condition. Citation information can be found in CITATION.cff.

CI Documentation PyPI

import msutils

info = msutils.msinfo("obs.ms")
print(info.render())

info.fields["PKS1934-638"].scan_numbers  # [1, 2]
info.spws[0].chan_width[0]  # 10000000.0
info.antennas["m003"].latitude  # -30.71053
$ msutils info obs.ms
Measurement Set: /data/obs.ms
  format      : MSv2    size: 271.4 MiB    rows: 595,200
  telescope   : MEERKAT      observer: sphe          project: TEST-2024-01
  observed    : 2024-01-01T00:00:00.000 -> 2024-01-01T05:12:44.000  (5h12m44s)
  totals      : 32 antennas, 496 baselines, 3 fields, 60 scans, 1 SPWs, 8 channels
  integration : 8s
  max baseline: 4,701.2 m

Fields (3):
  ID  Name         RA            Dec           Frame  Intents             Scans    Rows
  --  -----------  ------------  ------------  -----  ------------------  -----  ------
   0  PKS1934-638  01h20m12.845  -29d47m37.70  J2000  CALIBRATE_BANDPASS     20  198,400
   1  J0217+0144   02h51m53.240  -32d39m30.94  J2000  CALIBRATE_PHASE        20  198,400
   2  DEEP_2       04h23m33.635  -35d31m24.18  J2000  OBSERVE_TARGET         20  198,400
...

Install

Requires Python ≥ 3.11. The base install is just numpy + python-casacore

  • click, and covers everything except averaging, plots and MSv4.
pip install msutils                    # msinfo, columns, subset, flags, flagstats, diagnostics
pip install "msutils[plots]"           # + PNG summary plots (matplotlib)
pip install "msutils[average]"         # + time/channel averaging (codex-africanus)
pip install "msutils[msv4]"            # + read MSv4 processing sets (xarray, zarr)
pip install "msutils[xarray-ms]"       # + read an MSv2 through the MSv4 schema
pip install "msutils[reconstruction]"  # + experimental arcae reconstruction probes
pip install "msutils[convert]"         # + write MSv2 -> MSv4 (xradio)
pip install "msutils[all]"             # everything

msinfo

msinfo returns a typed, JSON-serialisable MSInfo whose collections are addressable by name as well as by id — because real work says "the bandpass calibrator", not "row 1".

info = msutils.msinfo("obs.ms")

info.observation.telescope  # 'MEERKAT'
info.fields["DEEP_2"].intents  # ['OBSERVE_TARGET#ON_SOURCE']
info.fields["DEEP_2"].ra_hms  # '04h23m33.635'
info.scans[7].duration  # 24.0  (includes the final integration)
info.spws["SPW0"].centre_freq  # 1415000000.0
info.data_descriptions[1].spw_id  # DDID -> SPW, explicitly
info.columns["DATA"].shape  # [4, 4]

info.to_dict()  # stable, versioned JSON
info.render(verbose=True)  # the listobs-style report above

Three cost levels, so inspecting a 100 GB MS doesn't have to read 100 GB:

level Reads Gives you
"meta" subtables only fields, SPWs, antennas, columns, max baseline
"full" (default) + one pass over the index columns scans, time ranges, row counts
"data" + UVW and FLAG uv coverage, flag statistics

"meta" is independent of MS size (~10 ms); "full" is one TaQL GROUPBY, flat in the number of scans.

MSv4 and engines

msinfo reads MSv2 tables and MSv4 processing sets into the same MSInfo:

msutils.msinfo("obs.ms")  # MSv2, via casacore/TaQL
msutils.msinfo("obs.zarr")  # MSv4, via xarray + zarr
msutils.msinfo("obs.ms", engine="xarray-ms")  # MSv2 through the MSv4 schema

The default MSv2 engine is casacore/TaQL: it needs nothing beyond the base install, it is several times faster for metadata, and it will open a Measurement Set that stricter readers reject — which matters, because a malformed MS is exactly the one you need to inspect. Pass engine="xarray-ms" when you want the n-dimensional MSv4 view of an MS you already have, with no conversion step.

Converting for real needs msutils[convert]:

msutils convert obs.ms obs.zarr

Everything else

# columns
msutils.addcol("obs.ms", "MODEL_DATA", clone="DATA")
msutils.copycol("obs.ms", "DATA", "CORRECTED_DATA")
msutils.sumcols("obs.ms", cols=["DATA", "MODEL_DATA"], outcol="CORRECTED_DATA")
msutils.delcol("obs.ms", "CORRECTED_DATA")  # reclaim the disk
msutils.renamecol("obs.ms", "MODEL_DATA", "OLD_MODEL")
msutils.addnoise("obs.ms", column="MODEL_DATA", sefd=551)

# datasets
msutils.subset("obs.ms", "target.ms", fields=["DEEP_2"], spws=[0])
msutils.subset("obs.ms", "parallel.ms", corrs=["XX", "YY"])  # names or zero-based indices
msutils.subset("obs.ms", "target.ms", fields=["DEEP_2"], chan_bin=4)  # select + average
msutils.average("obs.ms", "avg.ms", time_bin=8.0, chan_bin=4)  # [average]

# flags
stats = msutils.flagstats("obs.ms")
stats.by_correlation["XY"].percent
stats.by_channel[0]  # per-channel, per SPW
msutils.flag_backup("obs.ms", name="pre-rfi")
msutils.flag_restore("obs.ms", "pre-rfi")

# diagnostics
msutils.du("obs.ms")  # where the bytes went
msutils.check("obs.ms")  # MSv2 conformance
msutils.taql("SELECT DISTINCT FIELD_ID FROM $1", "obs.ms")

subset takes time_bin/chan_bin too, so selecting and averaging is one pass rather than two (it hands off to average, so that needs the average extra).

Both functions accept corrs=["XX", "YY"] (or repeatable --corr options) to keep correlations by name or zero-based index, in the requested order. Each polarization setup in the selection must contain them; data, flags, weights and polarization metadata follow the selection.

By default subset keeps the original field and SPW ids rather than renumbering them the way CASA split does, so ids in a subset still match the parent MS. Pass reindex=True (--reindex) for split's behaviour: the FIELD, SPECTRAL_WINDOW and DATA_DESCRIPTION rows the output no longer uses are dropped and the survivors are renumbered from 0, in their original order. ANTENNA, POLARIZATION, STATE and OBSERVATION keep every row.

Command line

msutils info      obs.ms [-v] [--level meta|full|data] [--json out.json]
msutils flagstats obs.ms [--plot flags.png] [--json flags.json] [--field DEEP_2]
msutils subset    obs.ms target.ms --field DEEP_2 --spw 0 [--corr XX --corr YY] [--chan-bin 4] [--reindex]
msutils average   obs.ms avg.ms --time-bin 8 --chan-bin 4 [--corr XX --corr YY] [--reindex]
msutils delcol    obs.ms CORRECTED_DATA MODEL_DATA
msutils renamecol obs.ms MODEL_DATA OLD_MODEL
msutils addcol    obs.ms MODEL_DATA --clone DATA
msutils copycol   obs.ms DATA CORRECTED_DATA
msutils sumcols   obs.ms DATA MODEL_DATA --out CORRECTED_DATA
msutils addnoise  obs.ms --column MODEL_DATA --sefd 551
msutils flags     backup|restore|list|delete obs.ms [NAME]
msutils du        obs.ms
msutils check     obs.ms
msutils taql      'SELECT DISTINCT FIELD_ID FROM $1' --ms obs.ms
msutils convert   obs.ms obs.zarr

msutils <command> --help for the full options.

Migrating from 2.x

summary() still works and returns the same dict shape, but is deprecated in favour of msinfo() and now emits a FutureWarning. Several of its values have been corrected — see CHANGELOG.md for the full list and for the removed 1.x aliases.

msutils.weights (MSNoise, SEFD-profile weight estimation) was removed in 3.0: it is data processing rather than an everyday MS operation. Pin msutils<3 if you still need it.

info = msutils.summary("obs.ms")  # deprecated
info = msutils.msinfo("obs.ms")  # use this

License

Apache License 2.0. See LICENSE and NOTICE.

Metadata

Release files for msutils 3.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for msutils 3.1.0
File Size Uploaded Provenance
msutils-3.1.0.tar.gz 161.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for msutils 3.1.0
File Interpreter ABI Platform
msutils-3.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 324.0 kB

Release files / msutils-3.1.0.tar.gz

Download URL msutils-3.1.0.tar.gz
Size 161.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2948a0aee8ea06035054f2146d77a184404094362eac637a92a70445c21578ff
BLAKE2b-256 checksum
How to use checksums
f378cf8525c029e20e49a67c6fabe1fd5d296b5c9c978669b2b4a241c8fba54a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / msutils-3.1.0-py3-none-any.whl

Download URL msutils-3.1.0-py3-none-any.whl
Size 162.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d611612d9469b7394a51e5006db2ddcc2cb718d395f339004f14ce286ce51ff7
BLAKE2b-256 checksum
How to use checksums
2eeceef665ed578c152d0f563dfb144eabbec84cadd2138debaaf0d586943f95
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for msutils 3.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page