Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Newio #1050

Merged
merged 7 commits into from
Oct 26, 2023
Merged

Newio #1050

Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"paramiko",
"nd2",
"sbxreader",
"h5py"
"h5py",
"opencv-python-headless"
]

nwb_deps = [
Expand Down
6 changes: 6 additions & 0 deletions suite2p/detection/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
n_frames, Ly, Lx = f_reg.shape
yrange = ops.get("yrange", [0, Ly]) if yrange is None else yrange
xrange = ops.get("xrange", [0, Lx]) if xrange is None else xrange
ops["yrange"] = yrange
ops["xrange"] = xrange

if mov is None:
bin_size = int(
Expand All @@ -138,6 +140,10 @@
elif mov.shape[2] != xrange[-1] - xrange[0]:
raise ValueError("mov.shape[2] is not same size as xrange")

if "meanImg" not in ops:
ops["meanImg"] = mov.mean(axis=0)
ops["max_proj"] = mov.max(axis=0)

Check warning on line 145 in suite2p/detection/detect.py

View check run for this annotation

Codecov / codecov/patch

suite2p/detection/detect.py#L144-L145

Added lines #L144 - L145 were not covered by tests

if ops.get("inverted_activity", False):
mov -= mov.min()
mov *= -1
Expand Down
20 changes: 16 additions & 4 deletions suite2p/gui/reggui.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from scipy.ndimage import gaussian_filter1d
from natsort import natsorted
from tifffile import imread
import json

from . import masks, views, graphics, traces, classgui, utils
from .. import registration
Expand Down Expand Up @@ -354,8 +355,7 @@ def plot_trace(self):
self.p2.setLimits(xMin=0, xMax=self.nframes)

def open(self):
filename = QFileDialog.getOpenFileName(self, "Open single-plane ops.npy file",
filter="ops*.npy")
filename = QFileDialog.getOpenFileName(self, "Open single-plane ops.npy file or single-plane ops.json file")
# load ops in same folder
if filename:
print(filename[0])
Expand Down Expand Up @@ -430,7 +430,19 @@ def openCombined(self, save_folder):

def openFile(self, filename, fromgui):
try:
ops = np.load(filename, allow_pickle=True).item()
ext = os.path.splitext(filename)[1]
if ext == ".npy":
ops = np.load(filename, allow_pickle=True).item()
dirname = os.path.dirname(filename)
elif ext == ".json":
with open(filename, "r") as f:
ops = json.load(f)
ops["Ly"] = ops["Lys"] if isinstance(ops["Lys"], int) else ops["Lys"][0]
ops["Lx"] = ops["Lxs"] if isinstance(ops["Lxs"], int) else ops["Lxs"][0]
dirname = os.path.join(os.path.dirname(filename), "suite2p/plane0/")
ops["reg_file"] = os.path.join(dirname, "data.bin")
nbytesread = np.int64(2 * ops["Ly"] * ops["Lx"])
ops["nframes"] = os.path.getsize(ops["reg_file"]) // nbytesread
self.LY = ops["Ly"]
self.LX = ops["Lx"]
self.Ly = [ops["Ly"]]
Expand All @@ -442,7 +454,7 @@ def openFile(self, filename, fromgui):
self.reg_loc = [ops["reg_file"]]
else:
self.reg_loc = [
os.path.abspath(os.path.join(os.path.dirname(filename), "data.bin"))
os.path.abspath(os.path.join(dirname, "data.bin"))
]
self.reg_file = [open(self.reg_loc[-1], "rb")]
self.wraw = False
Expand Down
2 changes: 1 addition & 1 deletion suite2p/gui/rungui.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def create_buttons(self):
self.inputformat = QComboBox()
[
self.inputformat.addItem(f)
for f in ["tif", "bruker", "sbx", "h5", "mesoscan", "haus", "nd2"]
for f in ["tif", "binary", "bruker", "sbx", "h5", "movie", "nd2", "mesoscan", "haus"]
]
self.inputformat.currentTextChanged.connect(self.parse_inputformat)
self.layout.addWidget(self.inputformat, 2, 0, 1, 1)
Expand Down
1 change: 1 addition & 0 deletions suite2p/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .nwb import save_nwb, read_nwb, nwb_to_binary
from .save import combined, compute_dydx, save_mat
from .sbx import sbx_to_binary
from .movie import movie_to_binary
from .tiff import mesoscan_to_binary, ome_to_binary, tiff_to_binary, generate_tiff_filename, save_tiff
from .nd2 import nd2_to_binary
from .binary import BinaryFile, BinaryFileCombined
Expand Down
210 changes: 210 additions & 0 deletions suite2p/io/movie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
try:
import cv2
HAS_CV2 = True
except:
HAS_CV2 = False

Check warning on line 5 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L4-L5

Added lines #L4 - L5 were not covered by tests

import numpy as np
import time
from typing import Optional, Tuple, Sequence
from .utils import find_files_open_binaries, init_ops

class VideoReader:
""" Uses cv2 to read video files """
def __init__(self, filenames: list):
""" Uses cv2 to open video files and obtain their details for reading

Parameters
------------
filenames : int
list of video files
"""
cumframes = [0]
containers = []
Ly = []
Lx = []
for f in filenames: # for each video in the list
cap = cv2.VideoCapture(f)
containers.append(cap)
Lx.append(int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)))
Ly.append(int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
cumframes.append(cumframes[-1] + int(cap.get(cv2.CAP_PROP_FRAME_COUNT)))
cumframes = np.array(cumframes).astype(int)
Ly = np.array(Ly)
Lx = np.array(Lx)
if (Ly==Ly[0]).sum() < len(Ly) or (Lx==Lx[0]).sum() < len(Lx):
raise ValueError("videos are not all the same size in y and x")

Check warning on line 36 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L22-L36

Added lines #L22 - L36 were not covered by tests
else:
Ly, Lx = Ly[0], Lx[0]

Check warning on line 38 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L38

Added line #L38 was not covered by tests

self.filenames = filenames
self.cumframes = cumframes
self.n_frames = cumframes[-1]
self.Ly = Ly
self.Lx = Lx
self.containers = containers
self.fs = containers[0].get(cv2.CAP_PROP_FPS)

Check warning on line 46 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L40-L46

Added lines #L40 - L46 were not covered by tests

def close(self) -> None:
"""
Closes the video files
"""
for i in range(len(self.containers)): # for each video in the list
cap = self.containers[i]
cap.release()

Check warning on line 54 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L52-L54

Added lines #L52 - L54 were not covered by tests

def __enter__(self):
return self

Check warning on line 57 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L57

Added line #L57 was not covered by tests

def __exit__(self, exc_type, exc_val, exc_tb):
self.close()

Check warning on line 60 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L60

Added line #L60 was not covered by tests

@property
def shape(self) -> Tuple[int, int, int]:
"""
The dimensions of the data in the file

Returns
-------
n_frames: int
The number of frames
Ly: int
The height of each frame
Lx: int
The width of each frame
"""
return self.n_frames, self.Ly, self.Lx

Check warning on line 76 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L76

Added line #L76 was not covered by tests

def get_frames(self, cframes):
"""
read frames "cframes" from videos

Parameters
------------
cframes : np.array
start and stop of frames to read, or consecutive list of frames to read
"""
cframes = np.maximum(0, np.minimum(self.n_frames - 1, cframes))
cframes = np.arange(cframes[0], cframes[-1] + 1).astype(int)

Check warning on line 88 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L87-L88

Added lines #L87 - L88 were not covered by tests
# find which video the frames exist in (ivids is length of cframes)
ivids = (cframes[np.newaxis, :] >= self.cumframes[1:, np.newaxis]).sum(axis=0)
nk = 0
im = np.zeros((len(cframes), self.Ly, self.Lx), "uint8")
for n in np.unique(ivids): # for each video in cumframes
cfr = cframes[ivids == n]
start = cfr[0] - self.cumframes[n]
end = cfr[-1] - self.cumframes[n] + 1
nt0 = end - start
capture = self.containers[n]
if int(capture.get(cv2.CAP_PROP_POS_FRAMES)) != start:
capture.set(cv2.CAP_PROP_POS_FRAMES, start)
fc = 0
ret = True
while fc < nt0 and ret:
ret, frame = capture.read()
if ret:
im[nk + fc] = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

Check warning on line 106 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L90-L106

Added lines #L90 - L106 were not covered by tests
else:
print("img load failed, replacing with prev..")
im[nk + fc] = im[nk + fc - 1]
fc += 1
nk += nt0
return im

Check warning on line 112 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L108-L112

Added lines #L108 - L112 were not covered by tests

def movie_to_binary(ops):
""" finds movie files and writes them to binaries

Parameters
----------
ops : dictionary
"nplanes", "data_path", "save_path", "save_folder", "fast_disk",
"nchannels", "keep_movie_raw", "look_one_level_down" (optional: "subfolders")

Returns
-------
ops : dictionary of first plane
"Ly", "Lx", ops["reg_file"] or ops["raw_file"] is created binary

"""
if not HAS_CV2:
raise ImportError("cv2 is required for this file type, please 'pip install opencv-python-headless'")

Check warning on line 130 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L129-L130

Added lines #L129 - L130 were not covered by tests

ops1 = init_ops(ops)

Check warning on line 132 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L132

Added line #L132 was not covered by tests

nplanes = ops1[0]["nplanes"]
nchannels = ops1[0]["nchannels"]

Check warning on line 135 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L134-L135

Added lines #L134 - L135 were not covered by tests

# open all binary files for writing
ops1, filenames, reg_file, reg_file_chan2 = find_files_open_binaries(ops1)

Check warning on line 138 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L138

Added line #L138 was not covered by tests

ik = 0
for j in range(ops["nplanes"]):
ops1[j]["nframes_per_folder"] = np.zeros(len(filenames), np.int32)

Check warning on line 142 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L140-L142

Added lines #L140 - L142 were not covered by tests


ncp = nplanes * nchannels
nbatch = ncp * int(np.ceil(ops1[0]["batch_size"] / ncp))
print(filenames)
t0 = time.time()
with VideoReader(filenames=filenames) as vr:
if ops1[0]["fs"]<=0:
for ops in ops1:
ops["fs"] = vr.fs

Check warning on line 152 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L145-L152

Added lines #L145 - L152 were not covered by tests

nframes_all = vr.cumframes[-1]
nbatch = min(nbatch, nframes_all)
nfunc = ops["functional_chan"] - 1 if nchannels > 1 else 0

Check warning on line 156 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L154-L156

Added lines #L154 - L156 were not covered by tests
# loop over all video frames
ik = 0

Check warning on line 158 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L158

Added line #L158 was not covered by tests
while 1:
irange = np.arange(ik, min(ik + nbatch, nframes_all), 1)
if irange.size == 0:
break
im = vr.get_frames(irange).astype("int16")
nframes = im.shape[0]
for j in range(0, nplanes):
if ik == 0:
ops1[j]["meanImg"] = np.zeros((im.shape[1], im.shape[2]),

Check warning on line 167 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L160-L167

Added lines #L160 - L167 were not covered by tests
np.float32)
if nchannels > 1:
ops1[j]["meanImg_chan2"] = np.zeros(

Check warning on line 170 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L169-L170

Added lines #L169 - L170 were not covered by tests
(im.shape[1], im.shape[2]), np.float32)
ops1[j]["nframes"] = 0
i0 = nchannels * ((j) % nplanes)
im2write = im[np.arange(int(i0) +

Check warning on line 174 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L172-L174

Added lines #L172 - L174 were not covered by tests
nfunc, nframes, ncp), :, :].astype(
np.int16)
reg_file[j].write(bytearray(im2write))
ops1[j]["meanImg"] += im2write.astype(np.float32).sum(axis=0)
if nchannels > 1:
im2write = im[np.arange(int(i0) + 1 -

Check warning on line 180 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L177-L180

Added lines #L177 - L180 were not covered by tests
nfunc, nframes, ncp), :, :].astype(
np.int16)
reg_file_chan2[j].write(bytearray(im2write))
ops1[j]["meanImg_chan2"] += im2write.astype(

Check warning on line 184 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L183-L184

Added lines #L183 - L184 were not covered by tests
np.float32).sum(axis=0)
ops1[j]["nframes"] += im2write.shape[0]

Check warning on line 186 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L186

Added line #L186 was not covered by tests
#ops1[j]["nframes_per_folder"][ih5] += im2write.shape[0]
ik += nframes
if ik % (nbatch * 4) == 0:
print("%d frames of binary, time %0.2f sec." %

Check warning on line 190 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L188-L190

Added lines #L188 - L190 were not covered by tests
(ik, time.time() - t0))

# write ops files
do_registration = ops1[0]["do_registration"]
for ops in ops1:
ops["Ly"] = im2write.shape[1]
ops["Lx"] = im2write.shape[2]
if not do_registration:
ops["yrange"] = np.array([0, ops["Ly"]])
ops["xrange"] = np.array([0, ops["Lx"]])
ops["meanImg"] /= ops["nframes"]
if nchannels > 1:
ops["meanImg_chan2"] /= ops["nframes"]
np.save(ops["ops_path"], ops)

Check warning on line 204 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L194-L204

Added lines #L194 - L204 were not covered by tests
# close all binary files and write ops files
for j in range(nplanes):
reg_file[j].close()
if nchannels > 1:
reg_file_chan2[j].close()
return ops1[0]

Check warning on line 210 in suite2p/io/movie.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/movie.py#L206-L210

Added lines #L206 - L210 were not covered by tests
10 changes: 5 additions & 5 deletions suite2p/io/sbx.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@

try:
from sbxreader import sbx_memmap
HAS_SBX = True
except:
print("Could not load the sbx reader, installing with pip.")
from subprocess import call
call("pip install sbxreader", shell=True)
from sbxreader import sbx_memmap

HAS_SBX = False

Check warning on line 14 in suite2p/io/sbx.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/sbx.py#L14

Added line #L14 was not covered by tests


def sbx_to_binary(ops, ndeadcols=-1, ndeadrows=0):
""" finds scanbox files and writes them to binaries
Expand All @@ -31,6 +29,8 @@
"Ly", "Lx", ops["reg_file"] or ops["raw_file"] is created binary

"""
if not HAS_SBX:
raise ImportError("sbxreader is required for this file type, please 'pip install sbxreader'")

Check warning on line 33 in suite2p/io/sbx.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/sbx.py#L32-L33

Added lines #L32 - L33 were not covered by tests

ops1 = init_ops(ops)
# the following should be taken from the metadata and not needed but the files are initialized before...
Expand Down
39 changes: 39 additions & 0 deletions suite2p/io/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,41 @@
print("** Found %d sbx - converting to binary **" % (len(fsall)))
return fsall, ops

def get_movie_list(ops):
""" make list of movie files to process
if ops["subfolders"], then all ops["data_path"][0] / ops["subfolders"] / *.avi or *.mp4
if ops["look_one_level_down"], then all tiffs in all folders + one level down
"""
froot = ops["data_path"]

Check warning on line 68 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L68

Added line #L68 was not covered by tests
# use a user-specified list of tiffs
if len(froot) == 1:
if "subfolders" in ops and len(ops["subfolders"]) > 0:
fold_list = []
for folder_down in ops["subfolders"]:
fold = os.path.join(froot[0], folder_down)
fold_list.append(fold)

Check warning on line 75 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L70-L75

Added lines #L70 - L75 were not covered by tests
else:
fold_list = ops["data_path"]

Check warning on line 77 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L77

Added line #L77 was not covered by tests
else:
fold_list = froot
fsall = []
for k, fld in enumerate(fold_list):
try:
fs = search_for_ext(fld, extension="mp4",

Check warning on line 83 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L79-L83

Added lines #L79 - L83 were not covered by tests
look_one_level_down=ops["look_one_level_down"])
fsall.extend(fs)
except:
fs = search_for_ext(fld, extension="avi",

Check warning on line 87 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L85-L87

Added lines #L85 - L87 were not covered by tests
look_one_level_down=ops["look_one_level_down"])
fsall.extend(fs)
if len(fsall) == 0:
print(fold_list)
raise Exception("No files, check path.")

Check warning on line 92 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L89-L92

Added lines #L89 - L92 were not covered by tests
else:
print("** Found %d movies - converting to binary **" % (len(fsall)))
return fsall, ops

Check warning on line 95 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L94-L95

Added lines #L94 - L95 were not covered by tests



def list_h5(ops):
froot = os.path.dirname(ops["h5py"])
Expand Down Expand Up @@ -249,6 +284,10 @@
fs, ops2 = get_nd2_list(ops1[0])
print("Nikon files:")
print("\n".join(fs))
elif input_format == "movie":
fs, ops2 = get_movie_list(ops1[0])
print("Movie files:")
print("\n".join(fs))

Check warning on line 290 in suite2p/io/utils.py

View check run for this annotation

Codecov / codecov/patch

suite2p/io/utils.py#L288-L290

Added lines #L288 - L290 were not covered by tests
else:
# find tiffs
fs, ops2 = get_tif_list(ops1[0])
Expand Down
Loading
Loading