Skip to content

Commit 74b96b8

Browse files
authored
Merge pull request #5 from tdincer/nd2support
Nd2support
2 parents f999cb0 + 1f8217a commit 74b96b8

File tree

3 files changed

+59
-39
lines changed

3 files changed

+59
-39
lines changed

docs/inputs.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ Scanbox binary files (*.sbx) work out of the box if you set ``ops['input_format'
148148
When recording in bidirectional mode some columns might have every other line saturated; to trim these during loading set ``ops['sbx_ndeadcols']``. Set this option to ``-1`` to let suite2p compute the number of columns automatically, a positive integer to specify the number of columns to trim.
149149
Joao Couto (@jcouto) wrote the binary sbx parser.
150150

151+
152+
Nikon nd2 files
153+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
154+
155+
Suite2p reads nd2 files using the nd2 package and returns a numpy array representing the data with a minimum of two dimensions (Height, Width). The data can also have additional dimensions for Time, Depth, and Channel. If any dimensions are missing, Suite2p adds them in the order of Time, Depth, Channel, Height, and Width, resulting in a 5-dimensional array. To use Suite2p with nd2 files, simply set ``ops['input_format'] = "nd2".``
156+
157+
158+
151159
BinaryRWFile
152160
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
153161

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
'h5py',
1515
'sbxreader',
1616
'scikit-learn',
17-
'cellpose']
17+
'cellpose',
18+
'nd2']
1819

1920
gui_deps = [
2021
"pyqt5",

suite2p/io/nd2.py

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
def nd2_to_binary(ops):
11-
""" finds nd2 files and writes them to binaries
11+
"""finds nd2 files and writes them to binaries
1212
1313
Parameters
1414
----------
@@ -39,72 +39,83 @@ def nd2_to_binary(ops):
3939
for file_name in fs:
4040
# open nd2
4141
nd2_file = nd2.ND2File(file_name)
42-
im = nd2_file.asarray()
42+
nd2_dims = {k: i for i, k in enumerate(nd2_file.sizes)}
4343

44-
# expand dimensions to have [Time (T), Depth (Z), Channel (C), Height (Y), Width (X)].
45-
if 'T' not in nd2_file.sizes:
46-
im = np.expand_dims(im, 0)
47-
if 'C' not in nd2_file.sizes:
48-
im = np.expand_dims(im, -3)
49-
if 'Z' not in nd2_file.sizes:
50-
im = np.expand_dims(im, 1)
44+
valid_dimensions = "TZCYX"
45+
assert set(nd2_dims) <= set(
46+
valid_dimensions
47+
), f"Unknown dimensions {set(nd2_dims)-set(valid_dimensions)} in file {file_name}."
5148

52-
nplanes = nd2_file.sizes['Z'] if 'Z' in nd2_file.sizes else 1
53-
nchannels = nd2_file.sizes['C'] if 'C' in nd2_file.sizes else 1
54-
nframes = nd2_file.sizes['T'] if 'T' in nd2_file.sizes else 1
49+
# Sort the dimensions in the order of TZCYX, skipping the missing ones.
50+
im = nd2_file.asarray().transpose(
51+
[nd2_dims[x] for x in valid_dimensions if x in nd2_dims]
52+
)
5553

56-
iblocks = np.arange(0, nframes, ops1[0]['batch_size'])
54+
# Expand array to include the missing dimensions.
55+
for i, dim in enumerate("TZC"):
56+
if dim not in nd2_dims:
57+
im = np.expand_dims(im, i)
58+
59+
nplanes = nd2_file.sizes["Z"] if "Z" in nd2_file.sizes else 1
60+
nchannels = nd2_file.sizes["C"] if "C" in nd2_file.sizes else 1
61+
nframes = nd2_file.sizes["T"] if "T" in nd2_file.sizes else 1
62+
63+
iblocks = np.arange(0, nframes, ops1[0]["batch_size"])
5764
if iblocks[-1] < nframes:
5865
iblocks = np.append(iblocks, nframes)
5966

6067
if nchannels > 1:
61-
nfunc = ops1[0]['functional_chan'] - 1
68+
nfunc = ops1[0]["functional_chan"] - 1
6269
else:
6370
nfunc = 0
6471

65-
if im.dtype.type == np.uint16:
66-
im = (im // 2).astype(np.int16)
67-
elif im.dtype.type == np.int32:
68-
im = (im // 2).astype(np.int16)
69-
elif im.dtype.type != np.int16:
70-
im = im.astype(np.int16)
72+
assert im.max() < 32768 and im.min() >= -32768, "image data is out of range"
73+
im = im.astype(np.int16)
7174

7275
# loop over all frames
7376
for ichunk, onset in enumerate(iblocks[:-1]):
7477
offset = iblocks[ichunk + 1]
7578
im_p = np.array(im[onset:offset, :, :, :, :])
76-
im2mean = im_p.mean(axis = 0).astype(np.float32) / len(iblocks)
79+
im2mean = im_p.mean(axis=0).astype(np.float32) / len(iblocks)
7780
for ichan in range(nchannels):
7881
nframes = im_p.shape[0]
7982
im2write = im_p[:, :, ichan, :, :]
8083
for j in range(0, nplanes):
8184
if iall == 0:
82-
ops1[j]['meanImg'] = np.zeros((im_p.shape[3], im_p.shape[4]), np.float32)
83-
if nchannels>1:
84-
ops1[j]['meanImg_chan2'] = np.zeros((im_p.shape[3], im_p.shape[4]), np.float32)
85-
ops1[j]['nframes'] = 0
85+
ops1[j]["meanImg"] = np.zeros(
86+
(im_p.shape[3], im_p.shape[4]), np.float32
87+
)
88+
if nchannels > 1:
89+
ops1[j]["meanImg_chan2"] = np.zeros(
90+
(im_p.shape[3], im_p.shape[4]), np.float32
91+
)
92+
ops1[j]["nframes"] = 0
8693
if ichan == nfunc:
87-
ops1[j]['meanImg'] += np.squeeze(im2mean[j, ichan, :, :])
88-
reg_file[j].write(bytearray(im2write[:, j, :, :].astype('int16')))
94+
ops1[j]["meanImg"] += np.squeeze(im2mean[j, ichan, :, :])
95+
reg_file[j].write(
96+
bytearray(im2write[:, j, :, :].astype("int16"))
97+
)
8998
else:
90-
ops1[j]['meanImg_chan2'] += np.squeeze(im2mean[j, ichan, :, :])
91-
reg_file_chan2[j].write(bytearray(im2write[:, j, :, :].astype('int16')))
92-
93-
ops1[j]['nframes'] += im2write.shape[0]
99+
ops1[j]["meanImg_chan2"] += np.squeeze(im2mean[j, ichan, :, :])
100+
reg_file_chan2[j].write(
101+
bytearray(im2write[:, j, :, :].astype("int16"))
102+
)
103+
104+
ops1[j]["nframes"] += im2write.shape[0]
94105
ik += nframes
95106
iall += nframes
96-
107+
97108
nd2_file.close()
98109

99110
# write ops files
100-
do_registration = ops1[0]['do_registration']
111+
do_registration = ops1[0]["do_registration"]
101112
for ops in ops1:
102-
ops['Ly'] = im.shape[3]
103-
ops['Lx'] = im.shape[4]
113+
ops["Ly"] = im.shape[3]
114+
ops["Lx"] = im.shape[4]
104115
if not do_registration:
105-
ops['yrange'] = np.array([0, ops['Ly']])
106-
ops['xrange'] = np.array([0, ops['Lx']])
107-
np.save(ops['ops_path'], ops)
116+
ops["yrange"] = np.array([0, ops["Ly"]])
117+
ops["xrange"] = np.array([0, ops["Lx"]])
118+
np.save(ops["ops_path"], ops)
108119
# close all binary files and write ops files
109120
for j in range(0, nplanes):
110121
reg_file[j].close()

0 commit comments

Comments
 (0)