Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add demo notebook. #153

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ LenslessPiCam
:target: https://lensless.readthedocs.io/en/latest/examples.html
:alt: notebooks

.. image:: https://img.shields.io/badge/Google_Slides-yellow
:target: https://docs.google.com/presentation/d/1PcNhMfjATSwcpbHUMrmc88ciQmheBJ7alz8hel8xnGU/edit?usp=sharing
:alt: slides

.. image:: https://huggingface.co/datasets/huggingface/badges/resolve/main/powered-by-huggingface-dark.svg
:target: https://huggingface.co/bezzam
:alt: huggingface
Expand Down
10 changes: 10 additions & 0 deletions docs/source/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ but they may not be the best way to get started with the library.
The following notebooks aim to provide a more interactive and intuitive
way to explore the different functionalities of the library.

This notebook provides a general overview of main features
(downloading/using datasets, PSF analysis/design, image recovery)
of the library:
(`GitHub <https://github.com/LCAV/LenslessPiCam/blob/main/notebook/lenslesspicam_demo.ipynb>`__,
`Google Colab <https://colab.research.google.com/drive/1q56Ht647JD5wocnrcT7rH5TCNK7FKmtH?usp=sharing>`__).
`Here <https://docs.google.com/presentation/d/1PcNhMfjATSwcpbHUMrmc88ciQmheBJ7alz8hel8xnGU/edit?pli=1#slide=id.p>`__
are accompanying slides that were given at PhD course to introduce computational lensless imaging.

The ones below are more specific and focus on different aspects of lensless cameras.

System / Hardware
-----------------

Expand Down
7 changes: 5 additions & 2 deletions lensless/hardware/mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ def create_mask(self):
self.height_map = height_map


def phase_retrieval(target_psf, wv, d1, dz, n=1.2, n_iter=10, height_map=False):
def phase_retrieval(target_psf, wv, d1, dz, n=1.2, n_iter=10, height_map=False, phase_wrap=1):
"""
Iterative phase retrieval algorithm similar to `PhlatCam <https://ieeexplore.ieee.org/document/9076617>`_,
using Fresnel propagation.
Expand All @@ -791,7 +791,10 @@ def phase_retrieval(target_psf, wv, d1, dz, n=1.2, n_iter=10, height_map=False):
Refractive index of the mask substrate. Default is 1.2.
n_iter: int
Number of iterations. Default value is 10.
phase_wrap : int
How many multiple of (2*pi) to wrap the phase, to limit heights. Default is 1.
"""
assert isinstance(phase_wrap, int), "phase_wrap should be an integer"
M_p = np.sqrt(target_psf)

if hasattr(d1, "__len__"):
Expand All @@ -809,7 +812,7 @@ def phase_retrieval(target_psf, wv, d1, dz, n=1.2, n_iter=10, height_map=False):
# constrain amplitude to be sqrt(PSF)
M_p = np.sqrt(target_psf) * np.exp(1j * np.angle(M_p))

phi = (np.angle(M_phi) + 2 * np.pi) % (2 * np.pi)
phi = (np.angle(M_phi) + 2 * np.pi) % (2 * np.pi * phase_wrap)

if height_map:
return phi, wv * phi / (2 * np.pi * (n - 1))
Expand Down
134 changes: 134 additions & 0 deletions lensless/utils/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,140 @@ def natural_sort(arr):
return sorted(arr, key=alphanum_key)


# available datasets
available_datasets = {
# -- DiffuserCam MirFlickr (7.58 GB) https://huggingface.co/datasets/bezzam/DiffuserCam-Lensless-Mirflickr-Dataset-NORM
"diffusercam_mirflickr": {
"size (GB)": 7.58,
"huggingface_repo": "bezzam/DiffuserCam-Lensless-Mirflickr-Dataset-NORM",
"psf": "psf.tiff",
"single_channel_psf": True,
"flipud": True,
"flip_lensed": True,
"downsample": 2,
"downsample_lensed": 2,
},
# -- TapeCam MirFlickr (10.5 GB) https://huggingface.co/datasets/bezzam/TapeCam-Mirflickr-25K
"tapecam_mirflickr": {
"size (GB)": 10.5,
"huggingface_repo": "bezzam/TapeCam-Mirflickr-25K",
"psf": "psf.png",
"display_res": [900, 1200],
"alignment": {"top_left": [45, 95], "height": 250},
},
# -- DigiCam CelebA (33.9 GB) https://huggingface.co/datasets/bezzam/DigiCam-CelebA-26K
"digicam_celeba": {
"size (GB)": 33.9,
"huggingface_repo": "bezzam/DigiCam-CelebA-26K",
"psf": "psf_simulated.png",
"rotate": True,
"split_seed": 0,
"downsample": 2,
"alignment": {"crop": {"vertical": [0, 525], "horizontal": [265, 695]}},
"simulation": {
"scene2mask": 0.25,
"mask2sensor": 0.002,
"object_height": 0.33,
"sensor": "rpi_hq",
"snr_db": None,
"downsample": None,
"random_vflip": False,
"random_hflip": False,
"quantize": False,
"vertical_shift": -117,
"horizontal_shift": -25,
},
},
# -- DigiCam MirFlickr (11.9 GB) https://huggingface.co/datasets/bezzam/DigiCam-Mirflickr-SingleMask-25K
"digicam_mirflickr": {
"size (GB)": 11.9,
"huggingface_repo": "bezzam/DigiCam-Mirflickr-SingleMask-25K",
"display_res": [900, 1200],
"rotate": True,
"alignment": {"top_left": [80, 100], "height": 200},
},
# DigiCam MirFlickr Mini (472 MB) https://huggingface.co/datasets/bezzam/DigiCam-Mirflickr-SingleMask-1K
"digicam_mirflickr_mini": {
"size (GB)": 0.472,
"huggingface_repo": "bezzam/DigiCam-Mirflickr-SingleMask-25K",
"display_res": [900, 1200],
"rotate": True,
"alignment": {"top_left": [80, 100], "height": 200},
},
# -- DigiCam MirFlickr Multimask (12 GB) https://huggingface.co/datasets/bezzam/DigiCam-Mirflickr-MultiMask-25K
"digicam_mirflickr_multi": {
"size (GB)": 12,
"huggingface_repo": "bezzam/DigiCam-Mirflickr-MultiMask-25K",
"display_res": [900, 1200],
"rotate": True,
"alignment": {"top_left": [80, 100], "height": 200},
},
# -- DigiCam MirFlickr Multimask Mini (477 MB) https://huggingface.co/datasets/bezzam/DigiCam-Mirflickr-MultiMask-1K
"digicam_mirflickr_multi_mini": {
"size (GB)": 0.477,
"huggingface_repo": "bezzam/DigiCam-Mirflickr-MultiMask-25K",
"display_res": [900, 1200],
"rotate": True,
"alignment": {"top_left": [80, 100], "height": 200},
},
# MultiLens MirFlickr Ambient (16.7 GB) https://huggingface.co/datasets/Lensless/MultiLens-Mirflickr-Ambient
"multilens_mirflickr_ambient": {
"size (GB)": 16.7,
"huggingface_repo": "Lensless/MultiLens-Mirflickr-Ambient",
"psf": "psf.png",
"display_res": [600, 600],
"alignment": {"top_left": [118, 220], "height": 123},
},
# MultiLens MirFlickr Ambient Mini (67.7 MB) https://huggingface.co/datasets/Lensless/MultiLens-Mirflickr-Ambient-100
"multilens_mirflickr_ambient_mini": {
"size (GB)": 0.0677,
"huggingface_repo": "Lensless/MultiLens-Mirflickr-Ambient-100",
"psf": "psf.png",
"display_res": [600, 600],
"alignment": {"top_left": [118, 220], "height": 123},
},
}


def print_available_datasets():
print("Available datasets:")
for dataset in available_datasets:
print(
f" - {dataset} ({available_datasets[dataset]['size (GB)']} GB) : https://huggingface.co/datasets/{available_datasets[dataset]['huggingface_repo']}"
)


def get_dataset(dataset_name, split, cache_dir=None, **kwargs):
"""
Get a dataset by name.

Parameters
----------
dataset_name : str
Name of the dataset from the available datasets in ``available_datasets``.
split : str
Split of the dataset to load (e.g. "train", "test").
cache_dir : str
Directory to cache the dataset. By default stored in ~/.cache/huggingface/datasets

Returns
-------
:py:class:`~torch.utils.data.Dataset`
Dataset object.
"""
if dataset_name not in available_datasets:
print_str_available_dataset = "Available datasets are:"
for dataset in available_datasets:
print_str_available_dataset += f"\n - {dataset} ({available_datasets[dataset]['size (GB)']} GB) : https://huggingface.co/datasets/{available_datasets[dataset]['huggingface_repo']}"
raise ValueError(
f"Dataset '{dataset_name}' not available.\n\n{print_str_available_dataset}"
)
assert split in ["train", "test"], "Split should be 'train' or 'test'"

dataset_config = available_datasets[dataset_name]
return HFDataset(split=split, cache_dir=cache_dir, **dataset_config, **kwargs)


class DualDataset(Dataset):
"""
Abstract class for defining a dataset of paired lensed and lensless images.
Expand Down
32 changes: 32 additions & 0 deletions lensless/utils/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,38 @@ def plot_autocorr2d(vals, pad_mode="reflect", ax=None):
return ax, autocorr


def plot_autocorr_rgb(img, width=3, figsize=None):
"""
Plot autocorrelation of each channel of an image.

Parameters
----------
img : py:class:`~numpy.ndarray`
2-D image.
width : int, optional
Width of cross-section to plot. Default is 3dB.

"""

assert len(img.shape) == 3, "Image must be 3D"
assert img.shape[2] == 3, "Image must have 3 color channels"

_, ax_auto = plt.subplots(ncols=3, nrows=2, num="Autocorrelations", figsize=figsize)

for i, c in enumerate(["r", "g", "b"]):
_, autocorr_c = plot_autocorr2d(img[:, :, i], ax=ax_auto[0][i])

ax, _ = plot_cross_section(
autocorr_c,
color=c,
ax=ax_auto[1][i],
plot_db_drop=width,
)
if i != 0:
ax.set_ylabel("")
return ax


def compare_models(model_paths, max_epoch=None, linewidth=2, fontsize=18, metrics=None):
"""
Plot train and test loss for multiple models, and print metrics for best epoch.
Expand Down
895 changes: 895 additions & 0 deletions notebook/lenslesspicam_demo.ipynb

Large diffs are not rendered by default.

Loading