Skip to content

Commit

Permalink
Merge pull request #1207 from girder/harden-nd2-source
Browse files Browse the repository at this point in the history
Harden the nd2 source to allow it to read more files.
  • Loading branch information
manthey authored Jun 14, 2023
2 parents c3d27c3 + f23723b commit 96e992f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 8 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## 1.22.5

### Improvements
- Harden the nd2 source to allow it to read more files ([#1207](../../pull/1207))

## 1.22.4

### Bug Fixes
Expand Down
57 changes: 49 additions & 8 deletions sources/nd2/large_image_source_nd2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def __init__(self, path, **kwargs):
basis *= self._nd2.sizes[k]
self.sizeX = self._nd2.sizes['X']
self.sizeY = self._nd2.sizes['Y']
self._nd2sizes = self._nd2.sizes
self.tileWidth = self.tileHeight = self._tileSize
if self.sizeX <= self._singleTileThreshold and self.sizeY <= self._singleTileThreshold:
self.tileWidth = self.sizeX
Expand All @@ -174,21 +175,57 @@ def __init__(self, path, **kwargs):
try:
self._frameCount = (
self._nd2.metadata.contents.channelCount * self._nd2.metadata.contents.frameCount)
self._bandnames = {
chan.channel.name.lower(): idx
for idx, chan in enumerate(self._nd2.metadata.channels)}
self._channels = [chan.channel.name for chan in self._nd2.metadata.channels]
except Exception:
self._frameCount = basis * self._nd2.sizes.get('C', 1)
self._channels = None
if not self._validateArrayAccess():
self._nd2.close()
del self._nd2
raise TileSourceError(
'File cannot be parsed with the nd2 source. Is it a legacy nd2 file?')
self._bandnames = {
chan.channel.name.lower(): idx for idx, chan in enumerate(self._nd2.metadata.channels)}
self._channels = [chan.channel.name for chan in self._nd2.metadata.channels]
self._tileLock = threading.RLock()

def __del__(self):
if hasattr(self, '_nd2'):
self._nd2.close()
del self._nd2

def _validateArrayAccess(self):
check = [0] * len(self._nd2order)
count = 1
for axisidx in range(len(self._nd2order) - 1, -1, -1):
axis = self._nd2order[axisidx]
axisSize = self._nd2.sizes[axis]
check[axisidx] = axisSize - 1
try:
self._nd2array[tuple(check)].compute()
if axis not in {'X', 'Y', 'S'}:
count *= axisSize
continue
except Exception:
if axis in {'X', 'Y', 'S'}:
return False
minval = 0
maxval = axisSize - 1
while minval + 1 < maxval:
nextval = (minval + maxval) // 2
check[axisidx] = nextval
try:
self._nd2array[tuple(check)].compute()
minval = nextval
except Exception:
maxval = nextval
check[axisidx] = minval
self._nd2sizes = {k: check[idx] + 1 for idx, k in enumerate(self._nd2order)}
self._frameCount = (minval + 1) * count
return True
self._frameCount = count
return True

def getNativeMagnification(self):
"""
Get the magnification at a particular level.
Expand Down Expand Up @@ -223,7 +260,7 @@ def getMetadata(self):

sizes = self._nd2.sizes
axes = self._nd2order[:self._nd2order.index('Y')][::-1]
sizes = self._nd2.sizes
sizes = self._nd2sizes
result['frames'] = frames = []
for idx in range(self._frameCount):
frame = {'Frame': idx}
Expand Down Expand Up @@ -255,9 +292,13 @@ def getInternalMetadata(self, **kwargs):
result['nd2_experiment'] = namedtupleToDict(self._nd2.experiment)
result['nd2_legacy'] = self._nd2.is_legacy
result['nd2_rgb'] = self._nd2.is_rgb
result['nd2_frame_metadata'] = [
diffObj(namedtupleToDict(self._nd2.frame_metadata(idx)), result['nd2'])
for idx in range(self._nd2.metadata.contents.frameCount)]
result['nd2_frame_metadata'] = []
try:
for idx in range(self._nd2.metadata.contents.frameCount):
result['nd2_frame_metadata'].append(diffObj(namedtupleToDict(
self._nd2.frame_metadata(idx)), result['nd2']))
except Exception:
pass
if (len(result['nd2_frame_metadata']) and
list(result['nd2_frame_metadata'][0].keys()) == ['channels']):
result['nd2_frame_metadata'] = [
Expand All @@ -273,7 +314,7 @@ def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs):
fc = self._frameCount
fp = frame
for axis in self._nd2order[:self._nd2order.index('Y')]:
fc //= self._nd2.sizes[axis]
fc //= self._nd2sizes[axis]
tileframe = tileframe[fp // fc]
fp = fp % fc
with self._tileLock:
Expand Down

0 comments on commit 96e992f

Please sign in to comment.