labdata 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ from .general import *
2
+ from .procedures import *
3
+ from .histology import *
4
+
5
+ @dataschema
6
+ class TwoPhoton(dj.Imported):
7
+ definition = '''
8
+ -> Dataset
9
+ ---
10
+ n_planes : smallint # number of planes
11
+ n_channels : smallint # number of channels
12
+ n_frames : int # duration of the recording
13
+ width : int # width of each frame
14
+ height : int # height of each frame
15
+ frame_rate : double # frame rate
16
+ -> File # path to the stack
17
+ magnification = NULL : double # magnification
18
+ objective_angle = NULL : double # angle
19
+ objective = NULL : varchar(32) # objective
20
+ um_per_pixel = NULL : blob # XY scale conversion factors
21
+ scanning_mode = NULL : varchar(32) # bidirectional or unidirectional
22
+ pmt_gain = NULL : blob # pmt gains
23
+ imaging_software : varchar(32) # software and version
24
+ -> [nullable] Atlas.Region # target region (or the center of the imaging plane)
25
+ '''
26
+
27
+ class Plane(dj.Part):
28
+ definition = '''
29
+ -> master
30
+ plane_num : smallint # probe number
31
+ ---
32
+ plane_depth = NULL : float # depth from surface
33
+ '''
34
+
35
+ def open(self):
36
+ # download and open a stack
37
+ if len(self) != 1:
38
+ raise(ValueError(f'Select only one dataset {self.proj().fetch(as_dict = True)}.'))
39
+ fname = (File() & self).get()[0]
40
+ return open_zarr(fname)
41
+
42
+ @analysisschema
43
+ class CellSegmentationParams(dj.Manual):
44
+ definition = '''
45
+ parameter_set_num : int # number of the parameters set
46
+ ---
47
+ algorithm_name : varchar(64) # cell segmentation algorithm
48
+ parameter_description = NULL : varchar(256) # description or specific use case
49
+ parameters_dict : varchar(2000) # parameters json formatted dictionary "json.dumps"
50
+ code_link = NULL : varchar(300) # link to the code
51
+ '''
52
+
53
+ @analysisschema
54
+ class CellSegmentation(dj.Imported):
55
+ definition = '''
56
+ -> Dataset
57
+ -> CellSegmentationParams
58
+ ---
59
+ algorithm_version : varchar(28) # version of the algorithm
60
+ num_rois : int # number of segmented ROIs
61
+ crop_region = NULL : blob # Region to crop
62
+ segmentation_datetime = NULL : datetime # date that the algorith was ran
63
+ -> [nullable] AnalysisFile # results file (optional)
64
+ '''
65
+
66
+ class Plane(dj.Part):
67
+ definition = '''
68
+ -> master
69
+ plane_num : smallint
70
+ ---
71
+ plane_num_rois : int
72
+ '''
73
+
74
+ class MotionCorrection(dj.Part):
75
+ definition = '''
76
+ -> master.Plane
77
+ ---
78
+ motion_block_size = 0 : int # 0 is rigid
79
+ displacement : longblob # storage of the motion displacement
80
+ '''
81
+ class Projection(dj.Part):
82
+ definition = '''
83
+ -> master.Plane
84
+ ---
85
+ proj_name : varchar(16)
86
+ proj_im : longblob
87
+ '''
88
+ class ROI(dj.Part):
89
+ definition = '''
90
+ -> master.Plane
91
+ roi_num : int
92
+ ---
93
+ roi_pixels : blob
94
+ roi_pixels_values = NULL : blob
95
+ neuropil_pixels = NULL : blob
96
+ '''
97
+ class RawTraces(dj.Part):
98
+ definition = '''
99
+ -> master.ROI
100
+ ---
101
+ f_trace : longblob
102
+ f_neuropil = NULL : longblob
103
+ '''
104
+ class Traces(dj.Part):
105
+ definition = '''
106
+ -> master.ROI
107
+ ---
108
+ dff : longblob
109
+ '''
110
+ class Deconvolved(dj.Part):
111
+ definition = '''
112
+ -> master.ROI
113
+ ---
114
+ deconv : longblob
115
+ '''
116
+
117
+ class Selection(dj.Part):
118
+ definition = '''
119
+ -> master.ROI
120
+ selection_method = 'auto' : varchar(12)
121
+ ---
122
+ selection : smallint # 0 not a cell, 1 is a cell
123
+ likelihood = NULL : float
124
+ '''
125
+
126
+ @analysisschema
127
+ class SegmentationROIMetrics(dj.Computed):
128
+ definition = '''
129
+ -> CellSegmentation.ROI
130
+ ---
131
+ roi_center : blob
132
+ radius = NULL : float
133
+ snr = NULL : float
134
+ aspect_ratio = NULL : float
135
+ skewness = NULL : float
136
+ std = NULL : float
137
+ noise_level = NULL : float
138
+ '''
139
+
140
+ def make(self, key):
141
+ # 90th percentile/np.median(np.abs(np.diff(dff))) / np.sqrt(frame_rate)?
142
+ return
@@ -0,0 +1,25 @@
1
+ from ..utils import *
2
+ # some functions used in the schema imports
3
+
4
+ __all__ = ['read_camlog']
5
+
6
+ def read_camlog(log):
7
+ '''
8
+ Adapted from github.com/jcouto/labcams
9
+ '''
10
+
11
+ logheaderkey = '# Log header:'
12
+ comments = []
13
+ with open(log,'r',encoding = 'utf-8') as fd:
14
+ for line in fd:
15
+ if line.startswith('#'):
16
+ line = line.strip('\n').strip('\r')
17
+ comments.append(line)
18
+ if line.startswith(logheaderkey):
19
+ columns = line.strip(logheaderkey).strip(' ').split(',')
20
+ camlog = pd.read_csv(log, delimiter = ',',
21
+ header = None,
22
+ comment = '#',
23
+ engine = 'c')
24
+ return comments,camlog
25
+
@@ -0,0 +1,243 @@
1
+ from .general import *
2
+
3
+ @dataschema
4
+ class DatasetVideo(dj.Manual):
5
+ definition = '''
6
+ -> Dataset
7
+ video_name : varchar(56)
8
+ ---
9
+ frame_times = NULL : longblob
10
+ frame_rate = NULL : float
11
+ n_frames = NULL : float
12
+ '''
13
+
14
+ class File(dj.Part):
15
+ definition = '''
16
+ -> master
17
+ -> File
18
+ '''
19
+
20
+ class Frame(dj.Part):
21
+ definition = '''
22
+ -> master
23
+ frame_num : int
24
+ ---
25
+ frame : longblob
26
+ '''
27
+
28
+ ######################################################
29
+ ############ POSE ESTIMATION ###############
30
+ ######################################################
31
+ @analysisschema
32
+ class PoseEstimationLabelSet(dj.Manual):
33
+ definition = '''
34
+ pose_label_set_num : int
35
+ ---
36
+ description = NULL : varchar(512)
37
+ -> [nullable] LabMember.proj(labeler = "user_name")
38
+ '''
39
+ class Frame(dj.Part):
40
+ definition = '''
41
+ -> master
42
+ -> DatasetVideo
43
+ frame_num : int
44
+ ---
45
+ frame : longblob
46
+ '''
47
+ class Label(dj.Part):
48
+ definition = '''
49
+ -> master
50
+ -> DatasetVideo
51
+ frame_num : int
52
+ label_name : varchar(54)
53
+ ---
54
+ x : float
55
+ y : float
56
+ z = NULL : float
57
+ '''
58
+ def export_labeling(self, model_num = None, bodyparts = None, disperse_labels = False, export_only_labeled = False):
59
+ '''
60
+ Exports labeling for PoseEstimation (for use with napari-deeplabcut)
61
+ '''
62
+ assert len(self) == 1, ValueError('PoseEstimationLabelSet, select only one set to export.')
63
+ k = self.proj().fetch1()
64
+
65
+ if export_only_labeled:
66
+ frames = pd.DataFrame((PoseEstimationLabelSet()*PoseEstimationLabelSet.Frame() & (PoseEstimationLabelSet.Label() & k)).fetch())
67
+ else:
68
+ frames = pd.DataFrame((PoseEstimationLabelSet()*PoseEstimationLabelSet.Frame() & k).fetch())
69
+ frame_labels = pd.DataFrame((PoseEstimationLabelSet()*PoseEstimationLabelSet.Label() & k).fetch())
70
+
71
+ if model_num is None:
72
+ folder = (Path(prefs['local_paths'][0])/'pose_estimation_models')/f'pose_label_set_num_{k["pose_label_set_num"]}'
73
+ else:
74
+ folder = (Path(prefs['local_paths'][0])/'pose_estimation_models')/f'model_{model_num}'
75
+ data_path = (folder / "labeled-data") / f'label_set_{k["pose_label_set_num"]}'
76
+ data_path.mkdir(parents=True, exist_ok=True)
77
+ if bodyparts is None:
78
+ bodyparts = np.unique(frame_labels.label_name.values)
79
+ from natsort import natsorted
80
+ bodyparts = natsorted(bodyparts) # this is an attempt to sort the labels
81
+ labeler = frames['labeler'].iloc[0]
82
+ from skimage.io import imsave
83
+ from tqdm import tqdm
84
+ todlc = []
85
+ for i,f in tqdm(enumerate(frames.frame_num.values),desc = "Exporting labeling dataset:",total = len(frames)):
86
+ im_name = 'im_{0:06d}_session{2}_frame{1:06d}'.format(i,f,frames.session_name.iloc[i])
87
+ for bpart in bodyparts:
88
+ t = (PoseEstimationLabelSet.Label & dict(
89
+ pose_label_set_num = k['pose_label_set_num'],
90
+ frame_num = f,
91
+ label_name = bpart)).fetch()
92
+ x = np.nan
93
+ y = np.nan
94
+ if disperse_labels:
95
+ if i == 0:
96
+ x = i*20
97
+ y = 100
98
+ if len(t):
99
+ x = t['x'][0]
100
+ y = t['y'][0]
101
+ todlc.append(dict(scorer = labeler,
102
+ bodyparts = bpart,
103
+ level_0 = 'labeled-data',
104
+ level_1 = f'label_set_{k["pose_label_set_num"]}',
105
+ level_2 = f'{im_name}.png',
106
+ x = x,
107
+ y = y))
108
+ fname = data_path/f'{im_name}.png'
109
+ if not fname.exists():
110
+ imsave(fname,frames.iloc[i].frame)
111
+ df = pd.DataFrame(todlc)
112
+ df = df.set_index(["scorer", "bodyparts","level_0","level_1","level_2"]).stack()
113
+ df.index.set_names("coords", level=-1, inplace=True)
114
+ df = df.unstack(["scorer", "bodyparts", "coords"])
115
+ df.index.name = None
116
+ df.to_hdf(data_path/f'CollectedData_{labeler}.h5',key='keypoints')
117
+ return data_path,frames,frame_labels
118
+
119
+ def update_labeling(self, labeling_file):
120
+ '''
121
+ (PoseEstimationLabelSet() & 'pose_label_set_num =3').update_labeling('filename.h5')
122
+
123
+ Updates the labels in the PoseEstimationLabelSet from a file.
124
+ Currently only DLC format is supported.
125
+
126
+ Reach out if you need other formats.
127
+ Joao Couto 2023
128
+ '''
129
+ dlcres = pd.read_hdf(labeling_file)
130
+ scorer = np.unique(dlcres.columns.get_level_values(0))[0]
131
+ bodyparts = np.unique(dlcres.columns.get_level_values(1))
132
+ frame_nums = [int(f.split('frame')[-1].strip('.png'))
133
+ for f in dlcres.reset_index()['level_2'].values]
134
+ frame_names = dlcres.reset_index()['level_2'].values
135
+ labels = []
136
+ from tqdm import tqdm
137
+ labels_to_insert = [] # insert the labels in parallel will be faster.
138
+ labels_to_delete = [] # need to delete all labels for a frame before adding the new ones
139
+ for iframe,frame_name in tqdm(enumerate(frame_names),desc = 'Updating labels',total = len(frame_names)):
140
+ frame_num = int(frame_name.split('frame')[-1].strip('.png'))
141
+ frame_key = dict(frame_num = frame_num)
142
+ if 'session' in frame_name: # get the session name so there are no conflicting frame numbers
143
+ frame_key['session_name'] = frame_name.split('session')[-1].split('_frame')[0]
144
+ frame_key = (PoseEstimationLabelSet.Frame() & self.proj().fetch1() & frame_key).proj().fetch1()
145
+ labels_to_delete.extend((PoseEstimationLabelSet.Label() & frame_key).proj().fetch(as_dict = True))
146
+ for dlcname in bodyparts:
147
+ if np.isnan(dlcres[scorer][dlcname].iloc[iframe]['x']):
148
+ continue # if it is NaN, don't add
149
+ if dlcres[scorer][dlcname].iloc[iframe]['x'] == 0 and dlcres[scorer][dlcname].iloc[iframe]['y'] == 0:
150
+ continue # if the label is at 0,0 don't add
151
+ label = dict(dict(frame_key,label_name = dlcname),
152
+ label_name = dlcname,
153
+ x = dlcres[scorer][dlcname].iloc[iframe]['x'],
154
+ y = dlcres[scorer][dlcname].iloc[iframe]['y'])
155
+ labels_to_insert.append(label)
156
+ (PoseEstimationLabelSet.Label() & labels_to_delete).delete(force = True) # ask the user to confirm
157
+ PoseEstimationLabelSet.Label.insert(labels_to_insert)
158
+
159
+
160
+ @analysisschema
161
+ class PoseEstimationModel(dj.Manual):
162
+ definition = '''
163
+ model_num : int
164
+ ---
165
+ algorithm_name : varchar(24) # Algorithm for pose estimation
166
+ -> AnalysisFile # zipped model; no videos.
167
+ -> [nullable] PoseEstimationLabelSet
168
+ parameters_dict = NULL : varchar(2000) # parameters json formatted dictionary
169
+ training_datetime = NULL : datetime
170
+ container_name = NULL : varchar(64) # Name of the container to use
171
+ code_link = NULL : varchar(300) # link to the github of the algorithm
172
+ '''
173
+
174
+ def insert_model(self, model_num,
175
+ model_folder=None,
176
+ pose_label_set_num = None,
177
+ algorithm_name = None,
178
+ parameters = None,
179
+ training_datetime=None,
180
+ container_name = None,
181
+ code_link = None):
182
+ import shutil
183
+ if training_datetime is None:
184
+ today = datetime.now()
185
+ else:
186
+ today = training_datetime
187
+ dataset_name = datetime.strftime(today,'%Y%m%d_%H%M%S')
188
+
189
+ # check if this model_number exists for another pose_label_set_num
190
+ allmodels = pd.DataFrame(PoseEstimationModel.fetch())
191
+ sel = allmodels[(allmodels.model_num.values == model_num) & (allmodels.pose_label_set_num.values != pose_label_set_num)]
192
+ if len(sel):
193
+ model_num = np.max(allmodels.model_num.values)+1
194
+
195
+ if model_folder is None:
196
+ model_folder = ((Path(prefs['local_paths'][0])/'pose_estimation_models')/f'{dataset_name}')/f'model_{model_num}'
197
+ filepath = ((Path(prefs['local_paths'][0])/'pose_estimation_models')/f'{dataset_name}')/f'model_{model_num}'
198
+ print(f'Creating archive {filepath}')
199
+ shutil.make_archive(filepath, 'zip', model_folder)
200
+ filepath = filepath.with_suffix('.zip')
201
+
202
+ key = AnalysisFile().upload_files([filepath],dataset = dict(subject_name = 'pose_estimation_models',
203
+ session_name = f'model_{model_num}',
204
+ dataset_name = dataset_name))
205
+ key = dict((AnalysisFile & key).proj().fetch1(),
206
+ model_num = model_num,
207
+ algorithm_name = algorithm_name,
208
+ training_datetime = today,
209
+ pose_label_set_num = pose_label_set_num,
210
+ parameters_dict = json.dumps(parameters) if not parameters is None else None,
211
+ container_name = container_name,
212
+ code_link = code_link)
213
+ if not len(PoseEstimationModel & f'model_num = {model_num}'):
214
+ self.insert1(key)
215
+ else:
216
+ print(f'Model {model_num} already exists. Updating but keeping last version in AWS.')
217
+ oldentry = (PoseEstimationModel & f'model_num = {model_num}').fetch1()
218
+ for k in key.keys():
219
+ if key[k] is None:
220
+ key[k] = oldentry[k]
221
+ self.update1(key)
222
+ # need to add a model evaluation part table here
223
+ def get_model(self):
224
+ filepath = (AnalysisFile & self).get()
225
+
226
+ filepath = filepath[0]
227
+ if not (filepath.parent/'config.yaml').exists():
228
+ import shutil
229
+ shutil.unpack_archive(filepath,extract_dir = filepath.parent)
230
+ return filepath.parent/'config.yaml' # return the path to the config file.
231
+
232
+ @analysisschema
233
+ class PoseEstimation(dj.Manual):
234
+ definition = '''
235
+ -> PoseEstimationModel
236
+ -> DatasetVideo
237
+ label_name : varchar(54)
238
+ ---
239
+ x : longblob
240
+ y : longblob
241
+ z = NULL : longblob
242
+ likelihood : longblob
243
+ '''
labdata/stacks.py ADDED
@@ -0,0 +1,182 @@
1
+ # files to process or visualize imaging stacks
2
+
3
+ from .utils import *
4
+
5
+ def downsample_stack(stack, ratio = [0.3,1,0.40625,0.40625],n_jobs = DEFAULT_N_JOBS,order = 1):
6
+ '''
7
+ Downsamples a 4D stack using interpolation.
8
+
9
+ smaller_stack = downsample_stack(stack, ratio = [0.3,1,0.40625,0.40625],n_jobs = DEFAULT_N_JOBS)
10
+
11
+ '''
12
+ assert len(stack.shape) == 4, ValueError('Only downsampling 4D stacks is supported.')
13
+ from scipy.ndimage import zoom
14
+ from joblib import Parallel, delayed
15
+ from tqdm import tqdm
16
+ import numpy as np
17
+ assert len(stack.shape) == len(ratio), ValueError(f'Stack ({stack.shape}) and ratio {len(ratio)} need to match')
18
+ a = Parallel(n_jobs = n_jobs)(delayed(zoom)(
19
+ frame,np.array(ratio)[1:],order = order)
20
+ for frame in tqdm(stack,
21
+ total = stack.shape[0],desc = 'Downsampling inner dimensions:'))
22
+ na = np.stack(a)
23
+ if na.shape[1] == 1:
24
+ return na
25
+ del a
26
+ na = Parallel(n_jobs = n_jobs)(delayed(zoom)(
27
+ na[:,i],[ratio[0],1,1],order = order)
28
+ for i in tqdm(range(na.shape[1]),
29
+ total = stack.shape[1],desc = 'Downsampling outer dimensions:'))
30
+ return np.stack(na).transpose([1,0,2,3])
31
+
32
+ def rotate_stack(stack,
33
+ anglez = 0,
34
+ angley = 0,
35
+ anglex = 0,
36
+ flip_x = False,
37
+ flip_y = False,
38
+ n_jobs = DEFAULT_N_JOBS,
39
+ order = 1):
40
+ '''
41
+ Rotate a stack a 4d stack.
42
+
43
+ This can be used for instance to rotate a whole brain image. Channels are rotated in paralell, careful with the
44
+ To get the angles:
45
+
46
+ Joao Couto - adapted from deeptrace (2023)
47
+ '''
48
+ from tqdm import tqdm
49
+ na = stack.transpose(1,0,2,3)
50
+ from scipy.ndimage import rotate
51
+ if anglex != 0.0:
52
+ na = Parallel(n_jobs = n_jobs)(delayed(rotate)(
53
+ s, angle = anglex,order = order,axes = [1,2],reshape = False)
54
+ for s in tqdm(na, total = na.shape[0],desc = 'Rotating the x axis:'))
55
+ na = np.stack(na)
56
+ if angley != 0.0:
57
+ na = Parallel(n_jobs = n_jobs)(delayed(rotate)(
58
+ s, angle = angley,order = order,axes = [0,2],reshape = False)
59
+ for s in tqdm(na,
60
+ total = na.shape[0],desc = 'Rotating the y axis:'))
61
+ na = np.stack(na)
62
+ if anglez != 0.0:
63
+ na = Parallel(n_jobs = n_jobs)(delayed(rotate)(
64
+ s, angle = anglez,order = order,axes = [0,1],reshape = False)
65
+ for s in tqdm(na,
66
+ total = na.shape[0],desc = 'Rotating the z axis:'))
67
+ na = np.stack(na)
68
+ if flip_x:
69
+ na = na[:,:,:,::-1]
70
+ if flip_y:
71
+ na = na[:,:,::-1,:]
72
+ return na.transpose(1,0,2,3)
73
+
74
+ def napari_open(stack,**kwargs):
75
+ '''
76
+ Example:
77
+
78
+ napari_open(stack.transpose(1,0,2,3),contrast_limits=[0,65000],channel_axis = 1,multiscale=False)
79
+
80
+ '''
81
+ import napari
82
+ napari.view_image(stack,**kwargs)
83
+
84
+ class VideoStack():
85
+ '''
86
+ Class to read video file sequences.
87
+ '''
88
+ def __init__(self,files, use_fast_seek = False):
89
+ self.N = 0
90
+ self.W = None
91
+ self.H = None
92
+ self.files = files
93
+ self.file_offsets = []
94
+ self.current_file = None
95
+ self.current_offset = -1
96
+ self.use_fast_seek = use_fast_seek
97
+ self.cap = None
98
+ self._read_properties()
99
+ self.dtype = np.uint8
100
+
101
+
102
+
103
+ def _read_properties(self):
104
+ self.file_offsets = [0]
105
+ self.N = 0
106
+ self.W = None
107
+ self.H = None
108
+ import cv2
109
+ for f in self.files:
110
+ cap = cv2.VideoCapture(f)
111
+ N = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
112
+ self.N += N
113
+ self.file_offsets.append(N)
114
+ if self.W is None:
115
+ self.W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
116
+ self.H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
117
+ if self.W != int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)):
118
+ raise(ValueError(f'Video {f} has a different width.'))
119
+ if self.H != int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)):
120
+ raise(ValueError(f'Video {f} has a different height.'))
121
+ cap.release()
122
+ self.shape = (self.N,self.H,self.W)
123
+ self.file_offsets = np.cumsum(self.file_offsets)
124
+ def __len__(self):
125
+ return self.shape[0]
126
+
127
+ def __getitem__(self, *args):
128
+ index = args[0]
129
+ idx1 = None
130
+ idx2 = None
131
+ if type(index) is tuple: # then look for 2 channels
132
+ if type(index[1]) is slice:
133
+ idx2 = range(index[1].start, index[1].stop, index[1].step)
134
+ else:
135
+ idx2 = index[1]
136
+ index = index[0]
137
+ if type(index) is slice:
138
+ idx1 = range(*index.indices(self.N))#start, index.stop, index.step)
139
+ elif type(index) in [int,np.int32, np.int64]: # just a frame
140
+ idx1 = [index]
141
+ else: # np.array?
142
+ idx1 = index
143
+ img = np.empty((len(idx1),*self.shape[1:]), dtype = self.dtype)
144
+ # print(img.shape,idx1)
145
+ for i,ind in enumerate(idx1):
146
+ img[i] = self._get_frame(ind)
147
+ if not idx2 is None:
148
+ return img[:,idx2].squeeze()
149
+ else:
150
+ return img[:].squeeze()
151
+
152
+ def _open_file(self,ifile):
153
+ if not self.cap is None:
154
+ self.cap.release()
155
+ self.cap = None
156
+ import cv2
157
+ self.cap = cv2.VideoCapture(self.files[ifile])
158
+ self.current_offset = 0
159
+ self.current_file = ifile
160
+ # print(f'Opened file {self.files[ifile]}.')
161
+
162
+ def _get_frame(self,ind):
163
+ import cv2
164
+ ifile = np.where(self.file_offsets <= ind)[0][-1]
165
+ if not self.current_file == ifile:
166
+ self._open_file(ifile)
167
+ ind = ind - self.file_offsets[self.current_file]
168
+ if ind == 0:
169
+ self._open_file(ifile) # open twice if zero because we may need to reset for sequential access
170
+ if self.use_fast_seek and not self.current_offset == ind:
171
+ self.cap.set(cv2.CAP_PROP_POS_FRAMES,ind)
172
+ ret, frame = self.cap.read()
173
+ self._open_file(ifile)
174
+ return frame[...,0]
175
+
176
+ while True:
177
+ ret, frame = self.cap.read()
178
+ self.current_offset += 1
179
+ if self.current_offset-1 == ind:
180
+ return frame[...,0]
181
+ elif (self.current_offset-1) > ind: # re-open the file
182
+ self._open_file(ifile)