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 ImageInterface #1190

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
* Added `metadata` and `conversion_options` as arguments to `NWBConverter.temporally_align_data_interfaces` [PR #1162](https://github.com/catalystneuro/neuroconv/pull/1162)

## Bug Fixes
* `run_conversion` does not longer trigger append mode an index error when `nwbfile_path` points to a faulty file [PR #1180](https://github.com/catalystneuro/neuroconv/pull/1180)
* `run_conversion` does not longer trigger append mode when `nwbfile_path` points to a faulty file [PR #1180](https://github.com/catalystneuro/neuroconv/pull/1180)
* `DatasetIOConfiguration` now recommends `chunk_shape = (len(candidate_dataset),)` for datasets with compound dtypes,
as used by hdmf >= 3.14.6.

## Features
* Use the latest version of ndx-pose for `DeepLabCutInterface` and `LightningPoseDataInterface` [PR #1128](https://github.com/catalystneuro/neuroconv/pull/1128)
* Added `ImageInterface` for writing large collection of images to NWB and automatically map the images to the correct NWB data types [PR #1190](https://github.com/catalystneuro/neuroconv/pull/1190)

## Improvements
* Simple writing no longer uses a context manager [PR #1180](https://github.com/catalystneuro/neuroconv/pull/1180)
* Added Returns section to all getter docstrings [PR #1185](https://github.com/catalystneuro/neuroconv/pull/1185)
* ElectricalSeries have better chunking defaults when data is passed as plain array [PR #1184](https://github.com/catalystneuro/neuroconv/pull/1184)
* ElectricalSeries have better chunking defaults when data is passed as an in-memory array [PR #1184](https://github.com/catalystneuro/neuroconv/pull/1184)
* Support Spikeinterface 0.102 [PR #1194](https://github.com/catalystneuro/neuroconv/pull/1194)
* Ophys interfaces now call `get_metadata` by default when no metadata is passed [PR #1200](https://github.com/catalystneuro/neuroconv/pull/1200)

Expand Down
118 changes: 118 additions & 0 deletions docs/user_guide/image.rst
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ben mentioned that a better place for this might be the conversion gallery and I think he is right.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it.

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
Image Interface
===============

The ImageInterface allows you to convert various image formats (PNG, JPG, TIFF) to NWB. It supports different color modes and efficiently handles image loading to minimize memory usage.

Supported Image Modes
---------------------

The interface supports the following PIL image modes:

- L (grayscale) → GrayscaleImage
- RGB → RGBImage
- RGBA → RGBAImage
- LA (luminance + alpha) → RGBAImage (automatically converted)

Example Usage
-------------

Here's an example demonstrating how to use the ImageInterface with different image modes:

.. code-block:: python

from datetime import datetime
from pathlib import Path
from neuroconv.datainterfaces import ImageInterface
from pynwb import NWBHDF5IO, NWBFile

# Create example images of different modes
from PIL import Image
import numpy as np

# Create a temporary directory for our example images
from tempfile import mkdtemp
image_dir = Path(mkdtemp())

# Create example images
# RGB image (3 channels)
rgb_array = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
rgb_image = Image.fromarray(rgb_array, mode='RGB')
rgb_image.save(image_dir / 'rgb_image.png')

# Grayscale image (L mode)
gray_array = np.random.randint(0, 255, (100, 100), dtype=np.uint8)
gray_image = Image.fromarray(gray_array, mode='L')
gray_image.save(image_dir / 'gray_image.png')

# RGBA image (4 channels)
rgba_array = np.random.randint(0, 255, (100, 100, 4), dtype=np.uint8)
rgba_image = Image.fromarray(rgba_array, mode='RGBA')
rgba_image.save(image_dir / 'rgba_image.png')

# LA image (luminance + alpha)
la_array = np.random.randint(0, 255, (100, 100, 2), dtype=np.uint8)
la_image = Image.fromarray(la_array, mode='LA')
la_image.save(image_dir / 'la_image.png')

# Initialize the image interface
interface = ImageInterface(folder_path=str(image_dir))

# Create a basic NWBFile
nwbfile = NWBFile(
session_description="Image interface example session",
identifier="IMAGE123",
session_start_time=datetime.now().astimezone(),
experimenter="Dr. John Doe",
lab="Image Processing Lab",
institution="Neural Image Institute",
experiment_description="Example experiment demonstrating image conversion",
)

# Add the images to the NWB file
interface.add_to_nwbfile(nwbfile)

# Write the NWB file
nwb_path = Path("image_example.nwb")
with NWBHDF5IO(nwb_path, "w") as io:
io.write(nwbfile)

# Read the NWB file to verify
with NWBHDF5IO(nwb_path, "r") as io:
nwbfile = io.read()
# Access the images container
images_container = nwbfile.acquisition["images"]
print(f"Number of images: {len(images_container.images)}")
# Print information about each image
for name, image in images_container.images.items():
print(f"\nImage name: {name}")
print(f"Image type: {type(image).__name__}")
print(f"Image shape: {image.data.shape}")

Key Features
------------

1. **Memory Efficiency**: Uses an iterator pattern to load images only when needed, making it suitable for large images or multiple images.

2. **Automatic Mode Conversion**: Handles LA (luminance + alpha) to RGBA conversion automatically while maintaining image information.

3. **Input Methods**:
- List of files: ``interface = ImageInterface(file_paths=["image1.png", "image2.jpg"])``
- Directory: ``interface = ImageInterface(folder_path="images_directory")``

4. **Flexible Storage Location**: Images can be stored in either acquisition or stimulus:
.. code-block:: python

# Store in acquisition (default)
interface = ImageInterface(file_paths=["image.png"], images_location="acquisition")

# Store in stimulus
interface = ImageInterface(file_paths=["image.png"], images_location="stimulus")

Installation
------------

To use the ImageInterface, install neuroconv with the image extra:

.. code-block:: bash

pip install "neuroconv[image]"
1 change: 1 addition & 0 deletions docs/user_guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and synchronize data across multiple sources.
:maxdepth: 2

datainterfaces
image
nwbconverter
adding_trials
temporal_alignment
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@ icephys = [
"neuroconv[abf]",
]

## Image
image = [
"pillow>=10.0.0", # PIL
]

## Ophys
brukertiff = [
"roiextractors>=0.5.10",
Expand Down Expand Up @@ -342,6 +347,7 @@ full = [
"neuroconv[behavior]",
"neuroconv[ecephys]",
"neuroconv[icephys]",
"neuroconv[image]",
"neuroconv[ophys]",
"neuroconv[text]",
]
Expand Down
8 changes: 8 additions & 0 deletions src/neuroconv/datainterfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
from .ophys.tdt_fp.tdtfiberphotometrydatainterface import TDTFiberPhotometryInterface
from .ophys.tiff.tiffdatainterface import TiffImagingInterface

# Image
from .image.imageinterface import ImageInterface

# Text
from .text.csv.csvtimeintervalsinterface import CsvTimeIntervalsInterface
from .text.excel.exceltimeintervalsinterface import ExcelTimeIntervalsInterface
Expand Down Expand Up @@ -164,6 +167,8 @@
# Text
CsvTimeIntervalsInterface,
ExcelTimeIntervalsInterface,
# Image
ImageInterface,
]

interfaces_by_category = dict(
Expand Down Expand Up @@ -200,4 +205,7 @@
ExcelTimeIntervals=ExcelTimeIntervalsInterface,
MedPC=MedPCInterface,
),
image=dict(
Image=ImageInterface,
),
)
5 changes: 5 additions & 0 deletions src/neuroconv/datainterfaces/image/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Image data interfaces."""

from .imageinterface import ImageInterface

__all__ = ["ImageInterface"]
Loading
Loading