Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Deprecated returning NWBFile when using `append_on_disk_nwbfile=True` in `write_imaging_to_nwbfile` and `write_segmentation_to_nwbfile`. Will return None on or after June 2026. [PR #1649](https://github.com/catalystneuro/neuroconv/pull/1649)

## Bug Fixes
* Fixed `TypeError: Object of type type is not JSON serializable` when passing `type` or `callable` objects (e.g. `progress_bar_class`) in `conversion_options`. The validation step now serializes these to their qualified module path for JSON schema validation while passing the original objects through to conversion unchanged. [PR #1667](https://github.com/catalystneuro/neuroconv/pull/1667)
* Fixed `UnicodeDecodeError` on Windows when reading YAML and JSON files containing UTF-8 characters by adding explicit `encoding='utf-8'` parameter to all text file operations. This ensures cross-platform compatibility per PEP 597 recommendations. [PR #1657](https://github.com/catalystneuro/neuroconv/pull/1657)
* Fixed bug in `write_imaging_to_nwbfile` where `nwbfile` was incorrectly passed to `add_imaging_to_nwbfile` instead of the created/loaded nwbfile. [PR #1649](https://github.com/catalystneuro/neuroconv/pull/1649)
* Fixed bug in `write_segmentation_to_nwbfile` where invalid `plane_num` parameter was passed to `add_segmentation_to_nwbfile`. [PR #1649](https://github.com/catalystneuro/neuroconv/pull/1649)
Expand Down
11 changes: 11 additions & 0 deletions src/neuroconv/utils/json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ class _NWBConversionOptionsEncoder(_GenericNeuroconvEncoder):
Custom JSON encoder for conversion options of the data interfaces and converters (i.e. kwargs).
"""

def default(self, obj):
# Serialize classes to their qualified name (e.g. progress_bar_class in iterator_options)
if isinstance(obj, type):
return f"{obj.__module__}.{obj.__qualname__}"

# Serialize callable objects (e.g. callback functions in progress_bar_options)
if callable(obj):
return f"{obj.__module__}.{obj.__qualname__}"

return super().default(obj)


# This is used in the Guide so we will keep it public.
NWBMetaDataEncoder = _NWBMetaDataEncoder
Expand Down
54 changes: 54 additions & 0 deletions tests/test_minimal/test_interface_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,57 @@ def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict | None, datetime_optio

nwbfile_path = tmp_path / "converter_test.nwb"
converter.run_conversion(nwbfile_path=nwbfile_path, overwrite=True, conversion_options=conversion_options)


def test_conversion_options_validation_with_type_and_callable(tmp_path):
"""Test that type objects and callables in conversion_options don't break validation.

This is a regression test for https://github.com/catalystneuro/neuroconv/pull/1667.
NWB GUIDE passes progress_bar_class (a type) and callback functions nested inside
iterator_opts dict in conversion_options, which caused TypeError during JSON
serialization in the validation step.
"""

class InterfaceWithIteratorOpts(MockInterface):

def add_to_nwbfile(
self,
nwbfile: NWBFile,
metadata: dict | None,
iterator_opts: dict | None = None,
):
pass

class MyProgressBar:
pass

def my_callback():
pass

iterator_opts = dict(
display_progress=True,
progress_bar_class=MyProgressBar,
progress_bar_options=dict(callback=my_callback),
)

interface = InterfaceWithIteratorOpts()

# Test with type object and callable via interface.run_conversion
nwbfile_path = tmp_path / "interface_test.nwb"
interface.run_conversion(
nwbfile_path=nwbfile_path,
iterator_opts=iterator_opts,
overwrite=True,
)

# Test with type object and callable via ConverterPipe.run_conversion
data_interfaces = {"InterfaceWithIteratorOpts": interface}
conversion_options = {
"InterfaceWithIteratorOpts": {
"iterator_opts": iterator_opts,
}
}
converter = ConverterPipe(data_interfaces=data_interfaces)

nwbfile_path = tmp_path / "converter_test.nwb"
converter.run_conversion(nwbfile_path=nwbfile_path, overwrite=True, conversion_options=conversion_options)
Loading