Skip to content

Commit

Permalink
Add ImageInterface (#1190)
Browse files Browse the repository at this point in the history
  • Loading branch information
h-mayorquin authored Feb 27, 2025
1 parent fa8a16f commit 3da382f
Show file tree
Hide file tree
Showing 9 changed files with 661 additions and 3 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
* Deprecations. in the ecephys pipeline: compression options, old iterator options, methods that did not end up in *to_nwbfile and the `get_schema_from_method_signature` function [PR #1207](https://github.com/catalystneuro/neuroconv/pull/1207)

## 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.
* Fixed AudioInterface to properly handle 24-bit WAV files by disabling memory mapping for 24-bit files [PR #1226](https://github.com/catalystneuro/neuroconv/pull/1226)

## 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)
as used by hdmf >= 3.14.6.
* Fixed AudioInterface to properly handle 24-bit WAV files by disabling memory mapping for 24-bit files [PR #1226](https://github.com/catalystneuro/neuroconv/pull/1226)
* Use the latest version of ndx-pose for `DeepLabCutInterface` and `LightningPoseDataInterface` [PR #1128](https://github.com/catalystneuro/neuroconv/pull/1128)
* Added a first draft of `.clinerules` [#1229](https://github.com/catalystneuro/neuroconv/pull/1229)
* Support for pynwb 3.0 [PR #1231](https://github.com/catalystneuro/neuroconv/pull/1231)
* Support for hdmf 4.0 [PR #1204](https://github.com/catalystneuro/neuroconv/pull/1204)
Expand Down
92 changes: 92 additions & 0 deletions docs/conversion_examples_gallery/imaging/image.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
Image data conversion
--------------------

The :py:class:`~neuroconv.datainterfaces.image.imageinterface.ImageInterface` allows conversion of various image formats (PNG, JPG, TIFF) to NWB. The interface efficiently handles different color modes and can store images in either the acquisition or stimulus group of the NWB file.

Install NeuroConv with the additional dependencies necessary for reading image data:

.. code-block:: bash
pip install "neuroconv[image]"
Supported Image Modes
~~~~~~~~~~~~~~~~~~~~

The interface automatically converts the following PIL image modes to their corresponding NWB types:

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

Example Usage
~~~~~~~~~~~~

.. code-block:: python
>>> from datetime import datetime
>>> from pathlib import Path
>>> from zoneinfo import ZoneInfo
>>> 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))
>>>
>>> # Get metadata from the interface
>>> metadata = interface.get_metadata()
>>> session_start_time = datetime(2020, 1, 1, 12, 30, 0, tzinfo=ZoneInfo("US/Pacific"))
>>> metadata["NWBFile"].update(session_start_time=session_start_time)
>>> # Choose a path for saving the nwb file and run the conversion
>>> nwbfile_path = f"{path_to_save_nwbfile}"
>>> interface.run_conversion(nwbfile_path=nwbfile_path, metadata=metadata)
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.

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

4. **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")
8 changes: 8 additions & 0 deletions docs/conversion_examples_gallery/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ Behavior
MedPC <behavior/medpc>


Image
-----

.. toctree::
:maxdepth: 1

Image (png, jpeg, tiff, etc) <imaging/image>

Text
----

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",
]

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

0 comments on commit 3da382f

Please sign in to comment.