Heidelberg Engineering E2E Format
Missing documentation of the Heidelberg E2E format has caused frustration by many working with OCT data and several projects tried to make the data accessible. Here you learn how to conveniently access data from E2E files using our HeE2eReader and what we know about the format.
Get to know your data
One thing that makes it especially difficult to read data from E2E files is that E2E is a general container format that can contain 0 or more instances of different kinds of data. If you are interested how the data is stored you might want to continue here.
For most users it is probably enough to print the HeE2eReader object to get an overview of Patients, Studies and Series stored in the file.
-
Printing an HeE2eReader results in something similar to this:
E2EFile E2EPatient(321) E2EStudy(1234) Device: Heidelberg Retina Angiograph - Studyname: NAME E2ESeries(50001) - Laterality: OD - B-scans: 241 Structure: Retina - Scanpattern: OCT ART Volume - Oct Modality: OCT - Enface Modality: IR E2ESeries(50002) - Laterality: OD - B-scans: 25 Structure: Retina - Scanpattern: OCT ART Volume - Oct Modality: OCT - Enface Modality: IR
Access data that can be parsed to EyeVolume objects
The HeE2eReader provides a convenient interface to access data stored in E2E files. Assuming that your E2E file contains one or more OCT volumes you can parse the volumes to EyeVolume objects using the following code:
from eyepy.io import HeE2eReader
with HeE2eReader("filename.E2E") as e2e_reader: # (1)
volumes = e2e_reader.volume
with HeE2eReader("filename.E2E", single=True) as e2e_reader: # (2)
volume = e2e_reader.volume
e2e_reader.volumereturnsList[EyeVolume]e2e_reader.volumereturns the first Series asEyeVolumeobject
Warning
- Currently we can not read scale Information for the localizer images as well as the x-scale of the B-scans. Hence quantifications can not be transformed to metric units.
- Also we know that in the E2E file B-scans are not registered with each other. This B-scan registration information has also not been found yet. This might cause problems when downstream analysis expects B-scans to be registered.
If you know how to read this information from the E2E file, please let us know by opening an issue
Access other data stored in E2E files
Not everything stored in an E2E file is accessible through parsing to EyeVolume objects. If you are interested in accessing other data stored in the E2E file, you can use the file hierarchy created by the HeE2eReader. The structure of the build hierarchy is shown in the diagram below. The file hierarchy can be accessed through the file_hierarchy attribute of the HeE2eReader object. You can either traverse the hierarchy level by level or access all elements of a specific level at once using one of the following attributes:
e2e_reader.patientsreturns a list of allE2EPatientStructureobjectse2e_reader.studiesreturns a list of allE2EStudyStructureobjectse2e_reader.seriesreturns a list of allE2ESeriesStructureobjects
First you might want to get an overview about the data stored in the hierarchy. Therefore you can use the following code:
from eyepy.io import HeE2eReader
with HeE2eReader("filename.E2E") as e2e_reader:
print(e2e_reader.inspect(recursive=True)) # (1)
- This method is basically an extended version of
print(HeE2eReader("filename.E2E"))that adds for every level of the hierarchy a table with information about the containded data.
E2E Hierarchy
classDiagram
E2EFileStructure *-- E2EPatientStructure
E2EPatientStructure *-- E2EStudyStructure
E2EStudyStructure *-- E2ESeriesStructure
E2ESeriesStructure *-- E2ESliceStructure
E2EStructureMixin <|-- E2EFileStructure
E2EStructureMixin <|-- E2EPatientStructure
E2EStructureMixin <|-- E2EStudyStructure
E2EStructureMixin <|-- E2ESeriesStructure
E2EStructureMixin <|-- E2ESliceStructure
class E2EStructureMixin{
- inspect(recursive, ind_prefix, tables)
- get_folder_data(folder_type, offset, data_construct)
}
class E2EFileStructure{
- folders: Dict[Union[TypesEnum, int], E2EFolder]
- substructure/patients: Dict[int, E2EPatientStructure]
}
class E2EPatientStructure{
- id: int
- folders: Dict[Union[TypesEnum, int], E2EFolder]
- substructure/studies: Dict[int, E2EStudyStructure]
}
class E2EStudyStructure{
- id: int
- folders: Dict[Union[TypesEnum, int], E2EFolder]
- substructure/series: Dict[int, E2ESeriesStructure]
}
class E2ESeriesStructure{
- id: int
- folders: Dict[Union[TypesEnum, int], E2EFolder]
- substructure/slices: Dict[int, E2ESlice]
- get_volume() -> EyeVolume
- get_layers() -> Dict[int, np.ndarray]
- get_localizer() -> EyeEnface
- get_localizer_meta() -> EyeEnfaceMeta
- get_meta() -> EyeVolumeMeta
- get_bscan_meta() -> List[EyeBscanMeta]
}
class E2ESliceStructure{
- id: int
- folders: Dict[Union[TypesEnum, int], E2EFolder]
- get_layers() -> Dict[int, np.ndarray]
- get_image() -> -> np.ndarray
- get_meta() -> EyeBscanMeta
}
If you have any further information on the E2E format or if you find any errors in this document, please let us know by opening an issue.
Open questions and differences to other Heidelberg Formats
- B-scan positions in the E2E format are given relative to an origin roughly in the center of the localizer image. We assume that the positions are given as angles in degree since the absolute value of minimum and maximum position is very close to half the field of view. This is different to VOL and XML formats where positions are given in mm with the origin in the top left corner of the localizer image. Since some position values indicate that they are located outside of the localizer image, we might have to apply the localizer transformation to them as well after mapping them to pixel indices.
- VOL and XML exports store the localizer scaling, as well as the scaling of the B-scans. The VOL format even stores the distance between the B-scans which has to be calculated from the B-scans in the XML and currently also the E2E format. We did not find this scaling information in the E2E format yet and use a hardcoded value for now. The only scaling we found was the Y Scale of the B-scan.
HEYEX Metadata Provenance
The E2ESeriesStructure now exposes a HEYEX-style metadata view:
from eyepy.io import HeE2eReader
with HeE2eReader("filename.E2E") as reader:
series = reader.series[0]
series_metadata = series.get_heyex_metadata()
bscan_metadata = series.get_heyex_metadata(bscan_index=0)
sources = series.get_heyex_metadata_sources(bscan_index=0)
volume = series.get_volume()
volume_bundle = volume.meta["e2e_metadata"]
The bscan_index argument matters because several OCT export fields are slice specific in HEYEX. If bscan_index is omitted, the reader leaves OCT Image -> ART Mode, Quality, and Acquisition Time as None.
When an E2E series is converted to an EyeVolume, the same information is also attached under volume.meta["e2e_metadata"]. This bundle contains the series-level HEYEX metadata, provenance, per-B-scan HEYEX OCT metadata, and the current provisional findings.
Field Scope
| Scope | Meaning |
|---|---|
patient |
Read from patient-level containers such as Type9 |
study |
Read from study-level containers such as Type13, Type9000, Type9001 |
series |
Read from series-level containers such as Type10005, Type9005-Type9008 |
bscan |
Read from slice/B-scan containers such as Type39 or Type10004 |
derived |
Computed directly from stored values |
provisional |
Strong empirical mapping, but not yet proven |
Provenance Table
| Export field | Scope | Provenance |
|---|---|---|
General Parameters -> Examination Time |
series |
Type10005@series+offset16 |
General Parameters -> Examined Structure |
series |
Type9005 |
General Parameters -> Application |
study/series |
Type13, with fallback to Type9005 |
General Parameters -> Scan Focus |
bscan/provisional |
Type10004@offset140 as float32, converted empirically |
IR Image -> ART Mode |
bscan |
Type39@offset376 |
IR Image -> Sensitivity (DC/DC) |
bscan |
Type39@offset8 |
IR Image -> Total Sensitivity |
bscan |
Type39@offset116 |
OCT Image -> ART Mode |
bscan |
Type10004@offset120 |
OCT Image -> Quality |
bscan |
Type10004@offset156 |
OCT Image -> Acquisition Time |
bscan |
Type10004.acquisitionTime |
OCT Image -> Scaling Z |
bscan |
Type10004.scale_y |
OCT Image -> Scaling X |
derived/provisional |
Type10004.start_x, end_x, and imgSizeWidth |
OCT Scan Pattern -> Number of B-Scans |
bscan |
Type10004.n_bscans |
OCT Scan Pattern -> Pattern Size |
derived/provisional |
angular span from Type10004, converted with the current Medium eye-length constant |
OCT Scan Pattern -> Distance between B-Scans |
derived/provisional |
vertical span from Type10004, converted with the current Medium eye-length constant |
Device -> Camera Model |
study |
Type13 |
Device -> Camera Model Code |
bscan |
ASCII model codes embedded in Type39 |
Device -> Camera/Power/Touch serial numbers |
bscan |
Type39@offset104, 108, 112 |
Device -> HRA/Power/Touch firmware |
bscan |
Type39@offset128, 132, 136 |
Device -> OCT Controller FW Version |
bscan |
Type10004@offset124 |
Device -> OCT Camera FW Version |
bscan/provisional |
Type10004@offset128 |
Device -> OCT Camera FPGA Version |
bscan/provisional |
Type10004@offset132 |
Device -> Acquisition Software Version |
bscan |
Type39@offset140 |
Provisional Mappings
Scan Focus currently uses the strongest candidate field found:
This fit matches our examples closely, but it is still empirical and should be treated as provisional.
Scaling X in µm/pixel also appears to be derived rather than stored directly:
The constant 289.6 µm/degree is the current best fit for the Medium eye-length preset. Additional paired files are still needed to confirm the conversion for Short and Long.
Current Caveats
Type40currently looks related to acquisition rotation / shear geometry rather than focus. In our sample set it changes for the rotated scan but not for the other focus-varying scans.OCT Camera FW VersionandOCT Camera FPGA Versionare assigned fromType10004@offset128andType10004@offset132respectively, but these two slots may need to be swapped later if a dataset with distinct values proves the order is reversed.Date of Birth,Resolution Mode,Camera Objective,Internal Target,External Target,A-Scan Rate,Eye Length,EDI Mode, andEVI Modeare still unresolved and are therefore returned asNoneby the HEYEX metadata helper.
Acknowledgements
While building the E2E file reader, and investigating the format we took inspiration from several existing projects, which we would like to thank: