Skip to content

fw-file

Introduction

fw-file is a Python package that provides a unified interface for working with various file formats commonly used in neuroimaging and medical research. It simplifies file parsing, metadata extraction, and format conversion, making it easier to work with complex file types in Flywheel and other applications.

Key Features

  • Unified Interface: Consistent API for working with different file formats
  • Metadata Extraction: Extract metadata from various file types
  • Format Conversion: Convert between different file formats
  • Validation: Validate file integrity and format compliance
  • Extension System: Extensible architecture for adding support for new file types

Supported File Types

fw-file supports a wide range of file formats, including:

Medical Imaging

  • DICOM
  • NIfTI1 and NIfTI2 (.nii.gz)
  • Bruker ParaVision (subject/acqp/method)
  • GE MR RAW / PFile (P_NNNNN_.7)
  • Philips MR PAR/REC header (.par)
  • Philips MR PAR/REC zipfile (.parrec.zip) (read-only)
  • Siemens MR RAW (.dat)
  • Siemens MR Spectroscopy (.rda)
  • Siemens PET RAW (.ptd)
  • PNG (.png)
  • JPEG/JPG (.jpeg/.jpg)
  • BrainVision EEG (.vhdr/.vmrk/.eeg)
  • EEGLAB EEG (.set/.fdt)
  • European Data Format EEG (.edf)
  • BioSemi Data Format EEG (.bdf)
  • JSON

Installation

1
2
3
4
5
6
pip install fw-file

# Optional dependencies
pip install "fw-file[dicom]"  # For DICOM support
pip install "fw-file[nifti]"  # For NIfTI support
pip install "fw-file[all]"    # For all supported formats

Usage Examples

Opening

from fw_file.dicom import DICOM
dcm = DICOM("dataset.dcm")  # also works with any readable file-like object

Fields

Attribute access on DICOMs works similarly to that in pydicom:

1
2
3
dcm.PatientAge == "060Y"
dcm.patientage == "060Y"   # attrs are case-insensitive
dcm.patient_age == "060Y"  # and snake_case compatible

Key access also returns values instead of pydicom.DataElement:

1
2
3
4
5
6
7
dcm["PatientAge"] == "060Y"
dcm["patientage"] == "060Y"   # keys are case-insensitive too
dcm["patient_age"] == "060Y"  # and snake_case compatible
dcm["00101010"] == "060Y"
dcm["0010", "1010"] == "060Y"
dcm[0x00101010] == "060Y"
dcm[0x0010, 0x1010] == "060Y"

Private tags can be accessed as keys when including the creator:

dcm["AGFA", "Zoom factor"] == 2
dcm["AGFA", "0019xx82"] == 2

Assignment and deletion works with attributes and keys alike:

dcm.PatientAge = "065Y"
del dcm["PatientAge"]

Metadata

Flywheel metadata can be extracted using the get_meta() method:

from fw_file.dicom import DICOM
dcm = DICOM("dataset.dcm")
dcm.get_meta() == {
    "subject.label": "PatientID",
    "session.label": "StudyDescription",
    "session.uid": "1.2.3",  # StudyInstanceUID
    "acquisition.label": "SeriesDescription",
    "acquisition.uid": "4.5.6",  # SeriesInstanceUID
    # and much, much more...
}

Saving

1
2
3
dcm.save()              # save to the original location
dcm.save("edited.dcm")  # save to a given filepath
dcm.save(io.BytesIO())  # save to any writable object

Collections and series

Handling multiple DICOM files together is a common use case, where the tags of more than one file need to be inspected in tandem for QA/validation or even modified for de-identification. DICOMCollection facilitates that and exposes convenience methods to be loaded from a list of files, a directory or a zip archive.

1
2
3
4
5
6
from fw_file.dicom import DICOMCollection
coll_dcm = DICOMCollection("001.dcm", "002.dcm")  # from a list of files
coll_dir = DICOMCollection.from_dir(".")          # from a directory
coll_zip = DICOMCollection.from_zip("dicom.zip")  # from a zip archive
coll = DICOMCollection()  # or start from scratch
coll.append("001.dcm")    # and add files later

To interact with the underlying DICOMs:

# access individual instances through list indexes
coll[0].SOPInstanceUID == "1.2.3"
# get tag value of all instances as a list, allowing different values
coll.bulk_get("SOPInstanceUID") == ["1.2.3", "1.2.4"]
# get a unique tag value, raising when encountering multiple values
coll.get("SeriesInstanceUID") == "1.2"
coll.get("SOPInstanceUID")  # raises ValueError
# set a tag value uniformly on all instances
coll.set("PatientAge", "060Y")
# delete a tag across all instances
coll.delete("PatientID")

Finally, a DICOMCollection can be saved in place, exported to a directory or packed as a zip archive:

1
2
3
coll.save()
coll.to_dir("/tmp/dicom")
coll.to_zip("/tmp/dicom.zip")

Resources