Skip to content

Commit

Permalink
Merge branch 'master' into origin/InputProcessing
Browse files Browse the repository at this point in the history
  • Loading branch information
mbeyeler committed Jun 9, 2022
2 parents 03b3118 + 9267655 commit 5470477
Show file tree
Hide file tree
Showing 12 changed files with 466 additions and 5 deletions.
3 changes: 3 additions & 0 deletions doc/topics/datasets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ functions that can be used to load datasets from the bionic vision community:
* :py:func:`~pulse2percept.datasets.fetch_beyeler2019`: Download and load
the phosphene drawing dataset from [Beyeler2019]_.

* :py:func:`~pulse2percept.datasets.fetch_han2021`: Download and load
the outdoor scenes from [Han2021]_.

.. note::

You will need Pandas (``pip install pandas``) to load the data.
Expand Down
6 changes: 4 additions & 2 deletions doc/topics/implants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ Implant Location Num Electrodes Manufacturer
---------- -------------- -------------- ---------------------------------
`ArgusI` epiretinal 16 Second Sight Medical Products Inc
`ArgusII` epiretinal 60 Second Sight Medical Products Inc
`IMIE` epiretinal 256 IntelliMicro Medical Co., Ltd
`AlphaIMS` subretinal 1500 Retina Implant AG
`AlphaAMS` subretinal 1600 Retina Implant AG
`PRIMA` subretinal 378 Pixium Vision SA
`PRIMA75` subretinal 142 Pixium Vision SA
`PRIMA55` subretinal 273(?) Pixium Vision SA
`PRIMA40` subretinal 532(?) Pixium Vision SA
`BVA24` suprachoroidal 24 Bionic Vision Technologies
`BVT24` suprachoroidal 24 Bionic Vision Technologies
`BVT44` suprachoroidal 44 Bionic Vision Technologies
========== ============== ============== =================================

Stimuli can be assigned to the various electrodes in the electrode array,
Expand Down Expand Up @@ -287,4 +289,4 @@ Make sure to specify an ``electric_potential`` method for your class:
.. minigallery:: pulse2percept.implants.Electrode
:add-heading: Examples using ``Electrode``
:heading-level: ~
:heading-level: ~
3 changes: 3 additions & 0 deletions doc/users/references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Studies referenced throughout the Documentation:
<https://doi.org/10.1002/cne.902920402>`_.
.. [Fleck1992] MM Fleck (1992). Some defects in finite-difference edge finders.
*IEEE J PAMI* 14:337--345
.. [Han2021] N Han, S Srivastava, A Xu, D Klein, M Beyeler (2021). Deep Learning–Based
Scene Simplification for Bionic Vision. *Augmented Humans Conference* 2021,
45–54. <https://doi.org/10.1145/3458709.3458982>`_.
.. [Granley2021] Granley, J., & Beyeler, M. (2021). A Computational Model of
Phosphene Appearance for Epiretinal Prostheses. *International
Conference of the IEEE Engineering in Medicine and Biology
Expand Down
19 changes: 19 additions & 0 deletions examples/implants/plot_implants.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,22 @@

AlphaAMS().plot(ax=ax[1])
ax[1].set_title('Alpha-AMS')

###############################################################################
# Intelligent Micro Implant Eye epiretinal prosthesis system (IMIE)
# ------------------------------------------------------------------
#
# :py:class:`~pulse2percept.implants.IMIE` is an epiretinal implant co-developed
# by Golden Eye Bionic, LLC (Pasadena CA) and IntelliMicro Medical Co., Ltd.
# (Changsha, Hunan Province, China) and is manufactured by IntelliMicro.
#
# IMIE consists of 248 large disc-shaped electrodes (210 µm in diameter) and 8
# smaller disc-shaped electrodes (160 µm in diameter), arranged on an area of
# 4.75 mm × 6.50 mm. The center-to-center pitch is 350 µm for the large electrodes
# and 300 µm for the small electrodes. [Xu2021]_.
#

fig, ax = plt.subplots(figsize=(10, 6))

IMIE().plot(ax=ax, annotate=True)
ax.set_title('IMIE')
2 changes: 2 additions & 0 deletions pulse2percept/datasets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from .base import clear_data_dir, get_data_dir, fetch_url
from .beyeler2019 import fetch_beyeler2019
from .han2021 import fetch_han2021
from .horsager2009 import load_horsager2009
from .nanduri2012 import load_nanduri2012
from .perezfornos2012 import load_perezfornos2012
Expand All @@ -34,6 +35,7 @@
'clear_data_dir',
'fetch_url',
'fetch_beyeler2019',
'fetch_han2021'
'get_data_dir',
'load_horsager2009',
'load_nanduri2012',
Expand Down
7 changes: 5 additions & 2 deletions pulse2percept/datasets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from os.path import exists, expanduser, join
from shutil import rmtree
import hashlib
import ssl
from urllib.request import urlretrieve


Expand Down Expand Up @@ -74,8 +75,8 @@ def _report_hook(count, block_size, total_size):
"""Display a progress bar for ``urlretrieve``"""
progress_size = int(count * block_size)
percent = min(100, int(count * block_size * 100 / total_size))
sys.stdout.write(f"\rDownloading {progress_size / (1024 * 1024)}"
f"/{total_size / (1024 * 1024)} MB ({percent}%)")
sys.stdout.write(f"\rDownloading {progress_size / (1024 * 1024):.1f}"
f"/{total_size / (1024 * 1024):.1f} MB ({percent}%)")
sys.stdout.flush()


Expand All @@ -100,6 +101,8 @@ def fetch_url(url, file_path, progress_bar=_report_hook, remote_checksum=None):
The expected SHA-256 checksum of the file.
"""
# Hacky way to keep using ulretrieve without SSL verification:
ssl._create_default_https_context = ssl._create_unverified_context
urlretrieve(url, file_path, progress_bar)
checksum = _sha256(file_path)
if remote_checksum != None and remote_checksum != checksum:
Expand Down
157 changes: 157 additions & 0 deletions pulse2percept/datasets/han2021.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
"""`fetch_han2021`"""
from os.path import join, isfile
import numpy as np

from .base import get_data_dir, fetch_url
from pulse2percept.stimuli import VideoStimulus

try:
import pandas as pd
has_pandas = True
except ImportError:
has_pandas = False

try:
import h5py
has_h5py = True
except ImportError:
has_h5py = False


def fetch_han2021(videos=None, resize=None, as_gray=None, data_path=None,
download_if_missing=True):
"""Load the original videos of outdoor scenes from [Han2021]_
Download the original videos or simulated prosthetic vision of outdoor
scenes described in [Han2021]_ from https://osf.io/pf2ja/ (303MB) to
``data_path``.
By default, all datasets are stored in '~/pulse2percept_data/', but a
different path can be specified.
=================== =====================
Number of videos: 20
Number of frames: 125 or 126
Frame size (px): 320 x 180
=================== =====================
Each :py:class:`~p2p.stimuli.VideoStimulus` object contains a `metadata`
dict with the following fields:
==================== ================================================
plugin FFMPEG
ffmpeg_version FFMPEG version
codec FFMPEG codec
pix_fmt pixel format
nframes Number of frames
duration Movie duration (seconds)
fps Frame rate (frames per second)
source File name of original video (before downscaling)
source_size Original image size (before downscaling)
source_shape Original video shape (before downscaling)
size Actual image size (after downscaling)
rotate Rotation angle
==================== ================================================
.. versionadded:: 0.9
Parameters
----------
videos: str | list of strings | None, optional
Video names you want to download. By default, all videos will be
downloaded. Available names: 'sample1' - 'sample4', 'stim1' - 'stim16'
resize : (height, width) or None, optional, default: None
A tuple specifying the desired height and width of each video frame.
The original size is 320x180 pixels.
as_gray : bool, optional
Flag whether to convert the image to grayscale.
A four-channel image is interpreted as RGBA (e.g., a PNG), and the
alpha channel will be blended with the color black.
data_path: string, optional
Specify another download and cache folder for the dataset. By default
all pulse2percept data is stored in '~/pulse2percept_data' subfolders.
download_if_missing : optional
If False, raise an IOError if the data is not locally available
instead of trying to download it from the source site.
Returns
-------
data: dict of VideoStimulus
VideoStimulus of the original videos in [Han2021]_
"""
if not has_h5py:
raise ImportError("You do not have h5py installed. "
"You can install it via $ pip install h5py.")
if not has_pandas:
raise ImportError("You do not have pandas installed. "
"You can install it via $ pip install pandas.")
# Create the local data directory if it doesn't already exist:
data_path = get_data_dir(data_path)

# Download the dataset if it doesn't already exist:
file_path = join(data_path, 'han2021.zip')
if not isfile(file_path):
if download_if_missing:
url = 'https://osf.io/pf2ja/download'
checksum = 'e31a74a6ac9decfa8d8b9eccd0c71da868f8dfa9f0475a4caca82085307d67b1'
fetch_url(url, file_path, remote_checksum=checksum)
else:
raise IOError(f"No local file {file_path} found")

# Open the HDF5 file:
hf = h5py.File(file_path, 'r')
data = dict()
if resize != None:
size = resize
else:
size = (320, 180)
if videos == None:
videos = hf.keys()
for key in videos:
vid = np.asarray(hf[key])
name = key[0:-4]
metadata = {'plugin': 'ffmpeg',
'nframes': vid.shape[3],
'ffmpeg_version': '4.2.2 built with gcc 9.2.1 (GCC) 20200122',
'codec': 'h264',
'pix_fmt': 'yuv420p(tv',
'fps': 25.0,
'source_size': (960, 540),
'size': size,
'rotate': 0,
'duration': vid.shape[3]/25.0,
'source': key,
'source_shape': (540, 960, 3, vid.shape[3])}
data[name] = VideoStimulus(vid, metadata=metadata, resize=resize,
as_gray=as_gray)
else:
if type(videos) == str:
videos = [videos]
for name in videos:
key = name+'.mp4'
if key not in hf.keys():
raise ValueError(
f"[Han2021]'s original videos do not include '{name}'"
f". Available names: 'sample1', 'sample2', 'sample3', 'sample4', "
f"'stim1', 'stim2', 'stim3', 'stim4', 'stim5', 'stim6', 'stim7', "
f"'stim8', 'stim9', 'stim10', 'stim11', 'stim12', 'stim13', "
f"'stim14', 'stim15', 'stim16'"
)
vid = np.asarray(hf[key])
metadata = {'plugin': 'ffmpeg',
'nframes': vid.shape[3],
'ffmpeg_version': '4.2.2 built with gcc 9.2.1 (GCC) 20200122',
'codec': 'h264',
'pix_fmt': 'yuv420p(tv',
'fps': 25.0,
'source_size': (960, 540),
'size': size,
'rotate': 0,
'duration': vid.shape[3]/25.0,
'source': key,
'source_shape': (540, 960, 3, vid.shape[3])}

data[name] = VideoStimulus(vid, metadata=metadata, resize=resize,
as_gray=as_gray)
hf.close()
return data
38 changes: 38 additions & 0 deletions pulse2percept/datasets/tests/test_han2021.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pulse2percept import datasets
import pytest
import numpy.testing as npt

def _is_han2021_not_available():
try:
datasets.fetch_han2021(download_if_missing=False)
return False
except IOError:
return True

@pytest.mark.skipif(
_is_han2021_not_available(),
reason='Download han2021 dataset to run this test'
)
def test_han2021():
data = datasets.fetch_han2021()
npt.assert_equal(len(data.keys()), 20)
npt.assert_equal(data['stim4'].vid_shape, (180, 320, 3, 125))
npt.assert_equal(data['stim5'].vid_shape, (180, 320, 3, 126))
npt.assert_equal(data['stim6'].metadata['source_size'], (960, 540))
npt.assert_equal(data['stim7'].metadata['source'], 'stim7.mp4')
npt.assert_almost_equal(data['stim8'].data[200,100], 0.16078432)

#check if resize works
data2 = datasets.fetch_han2021(resize = (18, 32))
npt.assert_equal(data2['sample1'].vid_shape, (18, 32, 3, 125))
npt.assert_almost_equal(data2['sample1'].data[100, 50], 0.1577467)

#check if as_gray worksk
data3 = datasets.fetch_han2021(['stim1','stim2'], as_gray = True)
npt.assert_equal(len(data3), 2)
npt.assert_equal(data3['stim1'].vid_shape, (180, 320, 125))
npt.assert_almost_equal(data3['stim2'].data[300,50], 0.08373686)

#check if value error throws when inputing invalid name
with pytest.raises(ValueError):
datasets.fetch_han2021('sti')
6 changes: 5 additions & 1 deletion pulse2percept/implants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
argus
alpha
bvt
imie
prima
.. seealso::
Expand All @@ -25,13 +26,15 @@
from .alpha import AlphaIMS, AlphaAMS
from .bvt import BVT24, BVT44
from .prima import PhotovoltaicPixel, PRIMA, PRIMA75, PRIMA55, PRIMA40
from .imie import IMIE

__all__ = [
'AlphaAMS',
'AlphaIMS',
'ArgusI',
'ArgusII',
'BVT24',
'BVT44',
'DiskElectrode',
'Electrode',
'ElectrodeArray',
Expand All @@ -44,5 +47,6 @@
'PRIMA55',
'PRIMA40',
'ProsthesisSystem',
'SquareElectrode'
'SquareElectrode',
'IMIE'
]

0 comments on commit 5470477

Please sign in to comment.