- Binary GFA file: overall structure
- Conventions
- File Header Section
- Segments Block
- Links Block
- Paths Block
- Walks Block
- Walks Type Format
- Walks and Paths: Type Note
- Packed Bits
- Conformance Requirements
- Appendix A: Example BGFA Files
We divide the file into the following parts. The first part is the file header, which contains some metadata. All other parts are a list of blocks. There can be several blocks of the same type.
- File Header
- List of Blocks (segments, links, paths, walks)
Each block contains a section_id field that identifies its type, allowing blocks
to appear in any order in the file.
| ID | Block Type | Description |
|---|---|---|
| 1 | Reserved | Formerly Segment Names; merged into Segments block |
| 2 | Segments | Segment names and sequences |
| 3 | Links | Link connections and CIGAR alignments |
| 4 | Paths | Paths with oriented segment IDs and CIGAR strings |
| 5 | Walks | Walks with sample/haplotype metadata and orientations |
Each block has a header and a payload.
The header contains all metadata, including the strategy used to compress data, the size of the compressed data, and
the size of the uncompressed data.
The payload contains the compressed data blobs.
For each header data we provide a type that allows us to determine its size. The only exception is the file header,
where the header_len field must also be read to determine the size of the file header.
- Record: A single entry within a block (e.g., one segment, one link, one walk).
- Block: A contiguous section of the file containing a header and a payload.
- Section: Synonym for block type, identified by
section_id. - Payload: The encoded data portion of a block, following the header.
- Field: A single information written in the file
To read a block from a BGFA file, a reader follows these steps:
- Read the block header: Parse the
section_id(1 byte) to determine the block type, then parse the remaining header fields according to the block type's header layout. - Determine payload size: The total payload size is the sum of all
compressed_*_lenfields in the header. Eachcompressed_*_lenincludes both the integer metadata list and the compressed blob for that field. - Parse each payload field: For each field, use the strategy code to determine how to decode it. The strategy
codes are different, depending on the type of data we want to store. For
stringsfields, read the metadata (integer positions list encoded per the first-byte strategy), then read the compressed blob (encoded per the second-byte strategy). Foruintsfields, the entire payload is the integer list encoded per the strategy code. Foruints-deltafields, the entire payload is the signed integer encoded delta list (see Signed Integers). Forbitsfields, read raw bits using the known bit count (see Packed Bits). - Reconstruct data: Use the metadata and strategy codes to decode each field back to its original form.
For implementation details of each encoding method, see encoding_algorithms.md.
Key principle: Every compressed_*_len field in the header tells you exactly how many bytes to read for its corresponding payload field. The reader sums these to find the total payload boundary, then processes each field sequentially within the payload.
| Field | Description | Type |
|---|---|---|
magic_number |
Magic number for bgfa files = "BGFA" (0x41464742) | uint32 |
version |
Format version (progressive number, starts at 0) | uint16 |
header_len |
length of the header string (excluding null terminator) | uint16 |
header_text |
GFA header text | bytes + null terminator |
Clarification: The header_text field is stored as header_len bytes of ASCII text followed by a single null terminator byte (\0). The header_len field specifies the length of the text portion only. Total bytes consumed = 8 (magic + version + header_len) + header_len + 1 (null terminator).
Note: The magic number 0x41464742 corresponds to ASCII "BGFA" when read as little-endian bytes: 0x42='B', 0x47='G', 0x46='F', 0x41='A'.
Version: The version field is a progressive integer starting at 0. It has no semantic meaning (no major/minor interpretation).
We associate an internal segment ID to each segment name, where the segment ID is an incrementing integer starting at 0. Segment IDs are assigned in the order segment names appear in the file.
| Field | Description | Type |
|---|---|---|
section_id |
Section type (2 = segments) | uint8 |
record_num |
number of records in the block | uint16 |
compression_segment_names |
Encoding strategy for the segment names (2 bytes) | bytes[2] |
compressed_segment_names_len |
length of compressed segment names field | uint64 |
uncompressed_segment_names_len |
sum of the lengths of the uncompressed segment names | uint64 |
compression_segment_label |
Encoding strategy for the segment sequences (2 bytes) | bytes[2] |
compressed_segment_label_len |
length of compressed segment_sequences field | uint64 |
uncompressed_segment_label_len |
sum of the lengths of uncompressed segment_sequences fields | uint64 |
The length of the uncompressed segment names does not include any terminator character.
| Field | Description | Type |
|---|---|---|
segment_names |
Segment names | strings |
sequences |
Segment sequences | strings |
Layout: The payload consists of encoded segment names followed by encoded sequences. We have two distinct encoding strategies.
Each block consists of a header and a payload.
The following is the sequence of fields making up the header.
| Field | Description | Type |
|---|---|---|
section_id |
Section type (3 = links) | uint8 |
record_num |
number of records in the block | uint16 |
| From/To field | ||
compression_fromto |
Encoding strategy for the from/to fields | bytes[2] |
compressed_fromto_len |
length of compressed from/to payload (metadata + blob) | uint64 |
| CIGAR field | ||
compression_links_cigars |
Encoding strategy for the cigar strings (4 bytes) | bytes[4] |
compressed_links_cigars_len |
length of compressed cigars payload (metadata + blob) | uint64 |
uncompressed_links_cigars_len |
sum of the lengths of uncompressed cigars | uint64 |
The following is the sequence of fields making up the payload layout.
Each field is padded so that it is aligned with a byte.
For example, if the list of from_ids requires 155 bits, it is padded with 5 additional zero bits, so that the overall
length is 20 bytes.
| Field | Description | Type |
|---|---|---|
from_ids |
Tail segment IDs (1-based; 0 = no connection) | uints |
to_ids |
Head segment IDs (1-based; 0 = no connection) | uints |
from_orientation |
Orientations of all from segments. 0 is +, 1 is - | bits (length = record_num) |
to_orientation |
Orientations of all to segments. 0 is +, 1 is - | bits (length = record_num) |
links_cigar_strings |
CIGAR strings | cigar strings |
Segment ID encoding: Segment IDs in the links payload are stored as 1-based indices into the segment list (value = internal_segment_id + 1, where internal IDs start at 0). The value 0 is reserved to indicate "no connection". The reader converts back to 0-based by subtracting 1.
Orientation mapping: The i-th bit in from_orientation corresponds to the i-th segment ID in from_ids. Similarly, the i-th bit in to_orientation corresponds to the i-th segment ID in to_ids.
Therefore there are exactly record_num segment IDs in both the from_ids and in the to_ids lists and there are exactly
record_num bits in both the from_orientation and in the to_orientations lists.
Orientation bits are stored with a least significant bit (LSB-first) strategy within each uint64 word. See the "Bits" section for details.
Each block consists of a header and a payload.
The following is the sequence of fields making up the header.
| Field | Description | Type |
|---|---|---|
section_id |
Section type (4 = paths) | uint8 |
record_num |
number of records in the block | uint16 |
compression_path_names |
Encoding strategy for the path names (2 bytes) | bytes[2] |
compressed_path_names_len |
length of compressed path names | uint64 |
uncompressed_path_names_len |
length of uncompressedpath names | uint64 |
compression_paths |
Encoding strategy for the paths as list of segment IDs (4 bytes) | bytes[2] |
compressed_paths_len |
length of compressed paths | uint64 |
uncompressed_paths_len |
sum of the lengths of uncompressed paths (as segment ID strings) | uint64 |
compression_paths_cigars |
Encoding strategy for the cigar strings (4 bytes) | bytes[4] |
compressed_paths_len_cigar |
length of compressed cigars | uint64 |
uncompressed_paths_len_cigar |
sum of the lengths of uncompressed cigars strings | uint64 |
The following is the sequence of fields making up the payload layout.
| Field | Description | Type |
|---|---|---|
path_names |
Path names | strings |
paths |
Paths, each path is a sequence of oriented segment IDs | walks |
paths_cigar_strings |
The list of CIGAR strings | cigar strings |
Each block consists of a header and a payload.
The following is the sequence of fields making up the header.
| Field | Description | Type |
|---|---|---|
section_id |
Section type (5 = walks) | uint8 |
record_num |
number of records in the block | uint16 |
| Compression strategies | ||
compression_sample_ids |
Encoding strategy for the sample IDs (2 bytes) | bytes[2] |
compression_hep |
Encoding strategy for the haplotype indices (2 bytes) | bytes[2] |
compression_sequence |
Encoding strategy for the sequence IDs | byte |
compression_positions_start |
Encoding strategy for the start positions | byte |
compression_positions_end |
Encoding strategy for the end positions | byte |
compression_walks |
Encoding strategy for the walks (4 bytes) | bytes[2] |
| Samples field | ||
compressed_sample_ids_len |
length of compressed sample IDs payload (metadata + blob) | uint64 |
uncompressed_sample_ids_len |
sum of the lengths of uncompressed sample IDs | uint64 |
| Haplotype indices field | ||
compressed_hep_len |
length of compressed haplotype indices payload (metadata + blob) | uint64 |
uncompressed_hep_len |
sum of the lengths of uncompressed haplotype indices | uint64 |
| Sequence IDs field | ||
compressed_sequence_len |
length of compressed sequence IDs payload (metadata + blob) | uint64 |
uncompressed_sequence_len |
sum of the lengths of uncompressed sequence IDs | uint64 |
| Positions field | ||
compressed_positions_len |
length of compressed positions payload (metadata + blob) | uint64 |
uncompressed_positions_len |
sum of the lengths of uncompressed positions | uint64 |
| Walks field | ||
compressed_walk_len |
length of compressed walks payload (metadata + blob) | uint64 |
uncompressed_walk_len |
sum of the lengths of uncompressed walks (total segment occurrences) | uint64 |
The following is the sequence of fields making up the payload layout.
| Field | Description | Type |
|---|---|---|
sample_ids |
Sample IDs | strings |
haplotype_indices |
Haplotype indices | uints |
sequence_ids |
Sequence IDs | strings |
start_positions |
Start positions | uints-delta |
end_positions |
End positions | uints-delta |
walks |
Walks, each walk is a sequence of oriented segment IDs | walks |
Since there are record_num records in the block, the block contains record_num sample IDs, followed by record_num
haplotype indices, followed by record_num sequence IDs, followed by record_num start positions, followed by
record_num end positions, followed by record_num walks.
Block ordering constraints:
- Blocks of the same type are not required to be contiguous
- Different section types can appear in any order relative to each other.
Endianness: All multi-byte integer fields (uint16, uint32, uint64) are encoded in little-endian format. Strategy codes and other byte sequences have no endianness considerations as they are simple sequences of bytes.
Alignment: All fields begin at the next byte boundary after the previous field. No inter-field padding is applied. Fields are packed contiguously without gaps.
All bytes data and all data that are encoded with a variable number of bits are packed into bytes. Any remaining bits in the final byte of the bitstream are set to 0 (when writing) and MUST be ignored (when reading).
C strings: The reference to C strings (ASCII terminated by \0) is for context only.
Input strings to the strings type do NOT include null terminators. Strings are stored as raw ASCII bytes without termination.
uints: A list of unsigned integers encoded according to the specified integer encoding strategy (see Encoding Method Reference).uints-delta: A list of unsigned integers encoded as delta differences. The first value is stored as-is; each subsequent value is the difference with the previous value. The resulting signed integers are encoded according to the signed integer encoding strategy (see Signed Integers).strings: A list of strings encoded according to the string encoding strategy. See String Encoding Overview for the encoding approach.cigar strings: A list of CIGAR strings encoded according to the cigar encoding strategy (see CIGAR-Specific Encoding).bits: A list of bits packed into uint64 words (see Packed Bits).walks: A list of walks, where each walk is a sequence of oriented segment IDs (see Walks Type Format).
The BGFA format uses strategy codes to specify encoding methods. Strategy codes are simple sequences of bytes with no endianness considerations. The code size depends on what is being encoded.
1-byte Strategy Codes (Integer-only):
Format: 0xMM
Bytes: [MM]
MM: Integer encoding method (0x00-0x0B)- Used for: Pure integer lists
- Examples:
0x01= Varint encoding0x02= Fixed16 encoding
2-byte Strategy Codes (Strings):
Format: 0xHHLL
Bytes: [HH, LL]
HH: Integer encoding method for metadata (lengths/positions)LL: String encoding method for blob- Used for: String lists
- Examples:
0x0102= Bytes[01, 02](Varint metadata + Fixed16 blob)0x0104= Bytes[01, 04](Varing metadata + LZMA blob)
2-byte Strategy Codes (Walks/Paths):
Format: 0xDD00IISS
Bytes: [HH, LL]
HH: Integer encoding method for metadata (lengths/positions)LL: Integer encoding method for the segment IDs- Examples:
0x0102= Bytes[01, 02](Varint metadata + Fixed16 for the segment IDs)0x0104= Bytes[01, 04](Varint metadata + elias gamma for the segment IDs)
4-byte Strategy Codes (CIGAR):
Format: 0xDDRRIISS
Bytes: [DD, RR, II, SS]
DD: Decomposition strategyRR: Reserved (must be 0x00)II: Integer encoding methodSS: String encoding method- Used for: CIGAR strings
- Example:
0x01000100= Bytes[01, 00, 01, 00](Identity decomposition, Varint integers, None for strings)
| Code Type | Format | Bytes | First | Second | Third | Fourth |
|---|---|---|---|---|---|---|
| Integer-only | 0xMM |
[MM] |
MM |
- | - | - |
| Strings | 0xHHLL |
[HH, LL] |
HH |
LL |
- | - |
| CIGAR | 0xDDRRIISS |
[DD, RR, II, SS] |
DD |
RR |
II |
SS |
| Walks/Paths | 0xHHLL |
[HH, LL] |
HH |
LL |
- | - |
The following tables map strategy code byte values to encoding methods. Implementation details for each method are in encoding_algorithms.md.
| Code | Strategy |
|---|---|
0x00 |
none (identity) |
0x01 |
varint |
0x02 |
fixed16 |
0x04 |
elias gamma |
0x05 |
elias omega |
0x06 |
golomb |
0x07 |
rice |
0x08 |
streamvbyte |
0x09 |
vbyte |
0x0A |
fixed32 |
0x0B |
fixed64 |
| Code | Strategy |
|---|---|
0x00 |
none (identity) |
0x01 |
zstd |
0x02 |
gzip |
0x03 |
lzma |
0x04 |
Huffman |
0x05 |
2-bit |
0x06 |
Arithmetic |
0x07 |
bzip2 |
0x08 |
RLE |
0x0A |
Dictionary |
0x0C |
LZ4 |
0x0D |
Brotli |
0x0E |
PPM |
| Format | Bytes | Strategy |
|---|---|---|
0x00?????? |
[00, ??, ??, ??] |
none (identity) |
0x01?????? |
[01, RR, II, SS] |
numOperations + lengths + operations |
0x02?????? |
[02, 00, II, SS] |
string (treat as plain string) |
See CIGAR 4-byte Strategy Code Details for wire formats.
- Readers MUST interpret strategy codes as simple byte sequences
- Readers MUST validate that reserved bytes in strategy codes are
0x00 - Readers MUST validate that strategy code byte patterns match the defined formats
- Readers MUST skip unknown section IDs without error
- Readers MUST treat unknown or invalid strategy codes as fatal errors
When encountering malformed data, readers should:
- Invalid strategy codes: Treat as fatal errors (cannot recover)
- Corrupted payloads: Skip the entire block if payload size can be determined from header
- Truncated files: Report error with bytes read vs expected
- Unknown section IDs: Skip the block using header size information
- Checksum failures: Treat as fatal errors if checksums are implemented
Different encoding strategies offer tradeoffs between compression ratio and speed:
- Fastest decompression: Identity (
0x00??), 2-bit DNA (0x??05) - Best compression: BWT+Huffman (
0x??07), Dictionary (0x??0A) - Balanced approach: Huffman (
0x??04), Zstd (0x??01) - Specialized: CIGAR-specific (
0x??09), Delta (0x03??) for sorted data
- Memory-mapped I/O: Useful for random access to large BGFA files
- Streaming processing: Process blocks sequentially for memory efficiency
- Parallel decompression: Different blocks can be decompressed in parallel
- Caching: Cache frequently accessed strategy code validations
# Valid 2-byte strategy code
def is_valid_2byte_strategy(code_bytes):
if len(code_bytes) != 2:
return False
first_byte, second_byte = code_bytes
# Check that bytes are within valid ranges
return (0x00 <= first_byte <= 0x0B) and (0x00 <= second_byte <= 0x0E)
# Valid 4-byte CIGAR strategy code
def is_valid_4byte_cigar_strategy(code_bytes):
if len(code_bytes) != 4:
return False
dd, rr, ii, ss = code_bytes
# Check decomposition strategy
if dd not in [0x00, 0x01, 0x02]:
return False
# Check reserved byte
if dd != 0x00 and rr != 0x00: # For non-identity, RR must be 0x00
return False
# Check integer encoding strategies
if ii not in [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B]:
return False
# Check string encoding strategies
if ss not in [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0C, 0x0D, 0x0E]:
return False
return TrueThe walks type encodes a list of walks, where each walk is a sequence of oriented segment IDs. This type is used in the Paths and Walks blocks.
Since we know that there are record_num walks in the block, we do not have to store that information again.
The walks layout is made by the following sequence of fields.
| Field | Description | Type |
|---|---|---|
walks_lengths |
The lengths of the walks | uints |
walks_segments |
The segment IDs of the walks, delta-encoded (see Type Definitions) | uints-delta |
walks_orientations |
The orientations of the walks | bits |
- All lengths of the walks are followed by all segment IDs of the walks, followed by all walks orientations.
- The
walks_lengthsfield contains exactlyrecord_numintegers. The i-th integer L(i) is the length of the i-th walk of the block. The sum of all L(i) equalsuncompressed_walk_lenfrom the containing block header. - The
walks_segmentsfield contains a sequence ofuncompressed_walk_lenintegers. The first L(1) integers form the sequence of segment IDs of the first walk, then the subsequent L(2) integers form the sequence of the segment IDs of the second walk, etc. - The
walks_orientationsfield contains a sequence ofuncompressed_walk_lenbits. The first L(1) bits form the sequence of segment orientations of the first walk, then the subsequent L(2) bits form the sequence of the segment orientations of the second walk. Orientation+or>is encoded as0, while-or<is encoded as 1.
walks_lengthsare encoded using the integer encoding strategy specified in the first byte of thecompression_walksfield in the containing block header.walks_segmentsare encoded using theuints-deltatype, which applies signed integer encoding (see Signed Integers). The integer encoding strategy is specified in the second byte of thecompression_walksfield in the containing block header and applies to the absolute values of the deltas. The values encoded are the difference between the current segment ID and the previous segment ID. For the first segment ID, we store the actual ID instead of the difference with the previous ID. For example, for the sequence of segment IDs [50, 48, 61] we compute deltas [50, -2, 13]. The sign bits are [0, 1, 0] and the absolute values [50, 2, 13] are encoded per the integer strategy.- the
walks_orientationsfield is always encoded as a bit sequence Packed Bits.
Example: Consider a walks block with 2 walks:
- Walk 1: "0+1-" (length 2, segments 0 and 1 with orientations + and -)
- Walk 2: "1+2+3-" (length 3, segments 1, 2 and 3 with orientation +, +, and -)
This would be encoded as:
walks_lengths: [2, 3] (encoded per the integer strategy in bytes 3-4)walks_segments: [0, 1, 0, 1, 1] (delta of the segment IDs for both walks concatenated)walks_orientations: [0, 1, 0, 0, 1] (0=+, 1=- for each segment, packed as bits)
Both the Walks and Paths blocks use the walks type to encode sequences of oriented segment IDs. The encoding is identical in both cases: a list of walks, where each walk is a sequence of segment IDs with orientations.
The key difference is in the surrounding metadata:
- Walks block: Each walk is associated with sample ID, haplotype, sequence ID, and positional metadata.
- Paths block: Each walk is associated with a path name and optional CIGAR strings.
The compression_walks (Walks block) and compression_paths (Paths block) fields both use 2-byte strategy codes from the Encoding Method Reference.
A bits field represents a list of bits packed into uint64 words in little-endian format.
Packing strategy:
- Bits are packed LSB-first within each
uint64word - Bit at index
iis stored at positioni % 64within wordi // 64 - The number of
uint64words isceil(n / 64)wherenis the number of bits - Unused bits in the final word (if any) are set to 0 when writing and MUST be ignored when reading
Determining the number of bits (n): The value of n depends on the context:
- Links block orientations (
from_orientation,to_orientation):n = record_numfrom the block header - Walks type orientations (
walks_orientations):n = uncompressed_walk_lenfrom the containing block header - Huffman encoded data:
nis determined by decoding exactly the required number of symbols (e.g.,2 * Lnibbles for a string of lengthL)
Size calculation: Number of uint64 words = ceil(n / 64). Total bytes for bits field = 8 * ceil(n / 64).
Example: For orientations [1, 0, 1, 1, 0, ...] (64 bits):
- Word 0 =
0b...01101(bit 0 = 1, bit 1 = 0, bit 2 = 1, bit 3 = 1, bit 4 = 0, ...)
Minimum reader requirements: A compliant BGFA reader MUST support all encodings:
Writer requirements: A compliant BGFA writer:
- MUST produce valid BGFA files that can be read by any compliant reader
- MUST set reserved fields to 0
- MUST NOT write empty blocks (blocks with
record_num = 0). If a block type has no records, omit the block entirely.
Error handling:
- A reader encountering an unknown
section_idMUST treat it as a fatal error. - A reader encountering an unknown strategy code MUST treat it as a fatal error.
- A reader encountering truncated or corrupted data MUST treat it as a fatal error.
To be provided.