Input and Output

Loading holograms

In our I/O module, we provide helpers for both loading data, and after reconstruction, saving the results in a simple numpy npz file.

from libertem_holo.base.io import InputData

# insert the full path to a dm3 or dm4 file here:
input_data = InputData.load_from_dm(path_to_data)

The data itself and the most important metadata is directly available:

print(f"shape: {input_data.shape}")
print(f"shape of the first image: {input_data.data[0].shape}")
print(f"pixel size: {input_data.pixelsize} m")
print(f"total expsure time: {input_data.exposure_time} s")

This code will print, for example:

shape: (20, 3838, 3710)
shape of the first image: (3838, 3710)
pixel size: 1.671157330274582e-10 m
total expsure time: 120.0 s

You can access the holograms one by one. This is a good pattern, as only memory for a single hologram is used at a time:

for idx in range(20):
    print(f"sum of image {idx}: {int(input_data.data[idx].sum())}")
sum of image 0: 833924288
sum of image 1: 834060160
sum of image 2: 833901824
sum of image 3: 833629056
sum of image 4: 833900736
sum of image 5: 833789056
sum of image 6: 833937536
sum of image 7: 833730560
sum of image 8: 833539456
sum of image 9: 833594048
sum of image 10: 833556544
sum of image 11: 833551680
sum of image 12: 833571072
sum of image 13: 833774208
sum of image 14: 833902912
sum of image 15: 833897024
sum of image 16: 834322880
sum of image 17: 834512896
sum of image 18: 834958976
sum of image 19: 835722176

Loading stacks from multiple files

The above example loads a stack from a single file. In many cases, holograms in a stack are saved to individual files. When using the libertem_holo.base.io.InputData class, there should be little difference if the data comes from a single-file or a multi-file stack. Multi-file data sets can be loaded with the libertem_holo.base.io.InputData.load_from_glob() method:

from libertem_holo.base.io import InputData

# insert the full path to a dm3 or dm4 file here:
input_data_glob = InputData.load_from_glob(
    base_path=glob_base_path,
    pattern="2018-7-17 15_29_*.dm4",
)
print(f"stack shape: {input_data_glob.shape}")
stack shape: (10, 3838, 3710)

The tags for each file can be accessed individually:

for idx in range(9):
    tags = input_data_glob.tags_for_slice(idx)
    print(f"timestamp {idx}: {tags['DataBar Acquisition Time (OS)']}")
    print(f"shape of hologram {idx}: {input_data_glob.data[idx].shape}")
timestamp 0: 1.3176307760097974e+17
shape of hologram 0: (3838, 3710)
timestamp 1: 1.31763077622961e+17
shape of hologram 1: (3838, 3710)
timestamp 2: 1.3176307764495226e+17
shape of hologram 2: (3838, 3710)
timestamp 3: 1.3176307766695352e+17
shape of hologram 3: (3838, 3710)
timestamp 4: 1.3176307768895477e+17
shape of hologram 4: (3838, 3710)
timestamp 5: 1.3176307771094603e+17
shape of hologram 5: (3838, 3710)
timestamp 6: 1.317630777329373e+17
shape of hologram 6: (3838, 3710)
timestamp 7: 1.3176307775494854e+17
shape of hologram 7: (3838, 3710)
timestamp 8: 1.317630777769398e+17
shape of hologram 8: (3838, 3710)

The libertem_holo.base.io.InputData.tags_for_slice() method can also be used with single-file stacks, but will give back the same tags for every slice.

Assumptions

We make a few assumptions:

  • Pixel sizes are given in m/px and are the same in X and Y direction

  • The data is either a single hologram or a 3D stack of holograms

  • The shapes and data types of individual holograms in multi-file data sets are matching

Note

If you are using UDFs for reconstruction, you need to use the I/O functionality of LiberTEM to load data. This is especially useful for reconstructing very large stacks.

Saving and loading results

Once you have reconstructed your data, you can save the results in a convenient format:

from libertem_holo.base.io import Results

res = Results(
    complex_wave=wave,
    unwrapped_phase=phase,
    brightfield=brightfield,
    metadata={"custom": 12.34},
)
# save_path is the full or relative path that ends in .npz
# where your data will be saved:
print(f"saving to {save_path}")
res.save(save_path)
saving to ...

You can also include metadata from the input data and reconstruction parameters in the results:

res.metadata_from_input(
    input_data=input_data,
    params=holo_params,  # a HoloParams instance
)
res.save(save_path)
print(f"effective pixel size: {res.metadata['effective_pixelsize']} m")
print(f"stack shape: {res.metadata['stack_shape']}")
print(f"total exposure time: {res.metadata['exposure_time']} s")
print(f"a custom value: {res.metadata['custom']}")

This code will print, for example:

effective pixel size: 0.2 m
stack shape: [1, 256, 256]
total exposure time: 24.6 s
a custom value: 12.34

The effective pixel size is the pixel size in the resulting phase image, which is the original pixel size adjusted for the chosen output shape. In case of stack reconstruction, the exposure time read from the input metadata is multiplied by the number of frames in the stack.

Everything but the complex wave is optional, so the minimal result looks like this:

from libertem_holo.base.io import Results

res = Results(
    complex_wave=wave,
)
print(f"saving to {save_path}")
res.save(save_path)
saving to ...