Skip to content

Commit 1aa68c0

Browse files
fix: handle MATLAB cell arrays with empty/nested elements (#1323)
Fix read_cell_array to handle edge cases from MATLAB: - Empty cell arrays ({}) - Cell arrays with empty elements ({[], [], []}) - Nested/ragged arrays ({[1,2], [3,4,5]}) - Cell matrices with mixed content The fix uses dtype='object' to avoid NumPy's array homogeneity requirements that caused reshape failures with ragged arrays. Closes #1056 Closes #1098 Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 8732f87 commit 1aa68c0

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

src/datajoint/blob.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,12 +474,30 @@ def pack_struct(self, array):
474474
) # values
475475

476476
def read_cell_array(self):
477-
"""deserialize MATLAB cell array"""
477+
"""
478+
Deserialize MATLAB cell array.
479+
480+
Handles edge cases from MATLAB:
481+
- Empty cell arrays ({})
482+
- Cell arrays with empty elements ({[], [], []})
483+
- Nested arrays ({[1,2], [3,4,5]}) - ragged arrays
484+
- Cell matrices with mixed content
485+
"""
478486
n_dims = self.read_value()
479487
shape = self.read_value(count=n_dims)
480488
n_elem = int(np.prod(shape))
481489
result = [self.read_blob(n_bytes=self.read_value()) for _ in range(n_elem)]
482-
return (self.squeeze(np.array(result).reshape(shape, order="F"), convert_to_scalar=False)).view(MatCell)
490+
491+
# Handle empty cell array
492+
if n_elem == 0:
493+
return np.empty(0, dtype=object).view(MatCell)
494+
495+
# Use object dtype to handle ragged/nested arrays without reshape errors.
496+
# This avoids NumPy's array homogeneity requirements that cause failures
497+
# with MATLAB cell arrays containing arrays of different sizes.
498+
arr = np.empty(n_elem, dtype=object)
499+
arr[:] = result
500+
return self.squeeze(arr.reshape(shape, order="F"), convert_to_scalar=False).view(MatCell)
483501

484502
def pack_cell_array(self, array):
485503
return (

tests/integration/test_blob_matlab.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,71 @@ def test_iter(schema_blob_pop):
160160
from_iter = {d["id"]: d for d in Blob()}
161161
assert len(from_iter) == len(Blob())
162162
assert from_iter[1]["blob"] == "character string"
163+
164+
165+
def test_cell_array_with_nested_arrays():
166+
"""
167+
Test unpacking MATLAB cell arrays containing arrays of different sizes.
168+
Regression test for issue #1098.
169+
"""
170+
# Create a cell array with nested arrays of different sizes (ragged)
171+
cell = np.empty(2, dtype=object)
172+
cell[0] = np.array([1, 2, 3])
173+
cell[1] = np.array([4, 5, 6, 7, 8])
174+
cell = cell.reshape((1, 2)).view(dj.MatCell)
175+
176+
# Pack and unpack
177+
packed = pack(cell)
178+
unpacked = unpack(packed)
179+
180+
# Should preserve structure
181+
assert isinstance(unpacked, dj.MatCell)
182+
assert unpacked.shape == (1, 2)
183+
assert_array_equal(unpacked[0, 0], np.array([1, 2, 3]))
184+
assert_array_equal(unpacked[0, 1], np.array([4, 5, 6, 7, 8]))
185+
186+
187+
def test_cell_array_with_empty_elements():
188+
"""
189+
Test unpacking MATLAB cell arrays containing empty arrays.
190+
Regression test for issue #1056.
191+
"""
192+
# Create a cell array with empty elements: {[], [], []}
193+
cell = np.empty(3, dtype=object)
194+
cell[0] = np.array([])
195+
cell[1] = np.array([])
196+
cell[2] = np.array([])
197+
cell = cell.reshape((3, 1)).view(dj.MatCell)
198+
199+
# Pack and unpack
200+
packed = pack(cell)
201+
unpacked = unpack(packed)
202+
203+
# Should preserve structure
204+
assert isinstance(unpacked, dj.MatCell)
205+
assert unpacked.shape == (3, 1)
206+
for i in range(3):
207+
assert unpacked[i, 0].size == 0
208+
209+
210+
def test_cell_array_mixed_empty_nonempty():
211+
"""
212+
Test unpacking MATLAB cell arrays with mixed empty and non-empty elements.
213+
"""
214+
# Create a cell array: {[1,2], [], [3,4,5]}
215+
cell = np.empty(3, dtype=object)
216+
cell[0] = np.array([1, 2])
217+
cell[1] = np.array([])
218+
cell[2] = np.array([3, 4, 5])
219+
cell = cell.reshape((3, 1)).view(dj.MatCell)
220+
221+
# Pack and unpack
222+
packed = pack(cell)
223+
unpacked = unpack(packed)
224+
225+
# Should preserve structure
226+
assert isinstance(unpacked, dj.MatCell)
227+
assert unpacked.shape == (3, 1)
228+
assert_array_equal(unpacked[0, 0], np.array([1, 2]))
229+
assert unpacked[1, 0].size == 0
230+
assert_array_equal(unpacked[2, 0], np.array([3, 4, 5]))

0 commit comments

Comments
 (0)