Skip to content

Commit fae313d

Browse files
committed
Merge branch 'master' into dev_classification
# Conflicts: # experiments/custom/experiments.py # requirements.txt # settings.ini # utils/poser.py
2 parents e94a646 + c455cc6 commit fae313d

22 files changed

+804
-477
lines changed

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: pip
4+
directory: "/"
5+
schedule:
6+
interval: monthly
7+
time: "04:00"
8+
open-pull-requests-limit: 10

DeepLabStream.py

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
import numpy as np
1818
import pandas as pd
1919

20-
from utils.generic import VideoManager, WebCamManager, GenericManager
21-
from utils.configloader import RESOLUTION, FRAMERATE, OUT_DIR, MODEL_NAME, MULTI_CAM, STACK_FRAMES, \
22-
ANIMALS_NUMBER, STREAMS, STREAMING_SOURCE, MODEL_ORIGIN
23-
from utils.plotter import plot_bodyparts, plot_metadata_frame
24-
from utils.poser import load_deeplabcut, load_dpk, load_dlc_live, get_pose, calculate_skeletons,\
25-
find_local_peaks_new, get_ma_pose
20+
from utils.generic import VideoManager, GenericManager
2621

22+
from utils.configloader import RESOLUTION, FRAMERATE, OUT_DIR, MODEL_NAME, MULTI_CAM, STACK_FRAMES, \
23+
ANIMALS_NUMBER, FLATTEN_MA, PASS_SEPARATE, STREAMS, STREAMING_SOURCE, MODEL_ORIGIN, CROP, CROP_X, CROP_Y
24+
from utils.plotter import plot_bodyparts,plot_metadata_frame
25+
from utils.poser import load_deeplabcut,load_dpk,load_dlc_live,load_sleap, get_pose,calculate_skeletons, \
26+
find_local_peaks_new,get_ma_pose
2727

2828
def create_video_files(directory, devices, resolution, framerate, codec):
2929
"""
@@ -146,6 +146,7 @@ def select_camera_manager():
146146
pylon_manager = PylonManager()
147147
manager_list.append(pylon_manager)
148148

149+
149150
def check_for_cameras(camera_manager):
150151
"""
151152
Helper method to get cameras, connected to that camera manager
@@ -168,17 +169,21 @@ def check_for_cameras(camera_manager):
168169

169170
MANAGER_SOURCE = {
170171
'video': VideoManager,
171-
'ipwebcam': WebCamManager,
172172
'camera': select_camera_manager
173173
}
174174

175+
# loading WebCam manager, if installed
176+
if find_spec("pyzmq") is not None:
177+
from utils.webcam import WebCamManager
178+
MANAGER_SOURCE['ipwebcam'] = WebCamManager()
179+
175180
# initialize selected manager
176181
camera_manager = MANAGER_SOURCE.get(STREAMING_SOURCE)()
177182
if camera_manager is not None:
178183
return camera_manager
179184
else:
180185
raise ValueError(f'Streaming source {STREAMING_SOURCE} is not a valid option. \n'
181-
f'Please choose from "video", "camera" or "ipwebcam".')
186+
f'Please choose from "video", "camera" or "ipwebcam". Make sure that if you are using "ipwebcam" you installed the additional dependencies.')
182187

183188
@property
184189
def cameras(self):
@@ -281,38 +286,61 @@ def get_pose_mp(input_q, output_q):
281286
while True:
282287
if input_q.full():
283288
index, frame = input_q.get()
289+
start_time = time.time()
284290
if MODEL_ORIGIN == 'DLC':
285291
scmap, locref, pose = get_pose(frame, config, sess, inputs, outputs)
286-
# TODO: Remove alterations to original
287292
peaks = find_local_peaks_new(scmap, locref, ANIMALS_NUMBER, config)
293+
#Use the line below to use raw DLC output rather then DLStream optimization
288294
# peaks = pose
289295
if MODEL_ORIGIN == 'MADLC':
290296
peaks = get_ma_pose(frame, config, sess, inputs, outputs)
291-
292-
output_q.put((index, peaks))
297+
analysis_time = time.time() - start_time
298+
output_q.put((index, peaks, analysis_time))
293299

294300
elif MODEL_ORIGIN == 'DLC-LIVE':
295301
dlc_live = load_dlc_live()
296302
while True:
297303
if input_q.full():
298304
index, frame = input_q.get()
305+
start_time = time.time()
299306
if not dlc_live.is_initialized:
300307
peaks = dlc_live.init_inference(frame)
301308
else:
302309
peaks = dlc_live.get_pose(frame)
303-
304-
output_q.put((index, peaks))
310+
analysis_time = time.time() - start_time
311+
output_q.put((index, peaks, analysis_time))
305312

306313
elif MODEL_ORIGIN == 'DEEPPOSEKIT':
307314
predict_model = load_dpk()
308315
while True:
309316
if input_q.full():
310317
index, frame = input_q.get()
318+
start_time = time.time()
311319
frame = frame[..., 1][..., None]
312320
st_frame = np.stack([frame])
313321
prediction = predict_model.predict(st_frame, batch_size=1, verbose=True)
314322
peaks = prediction[0, :, :2]
315-
output_q.put((index, peaks))
323+
analysis_time = time.time() - start_time
324+
output_q.put((index,peaks,analysis_time))
325+
326+
elif MODEL_ORIGIN == 'SLEAP':
327+
sleap_model = load_sleap()
328+
while True:
329+
if input_q.full():
330+
index, frame = input_q.get()
331+
start_time = time.time()
332+
input_frame = frame[:, :, ::-1]
333+
#this is weird, but without it, it does not seem to work...
334+
frames = np.array([input_frame])
335+
prediction = sleap_model.predict(frames[[0]], batch_size=1)
336+
#check if this is multiple animal instances or single animal model
337+
if sleap_model.name == 'single_instance_inference_model':
338+
#get predictions (wrap it again, so the behavior is the same for both model types)
339+
peaks = np.array([prediction['peaks'][0, :]])
340+
else:
341+
peaks = prediction['instance_peaks'][0, :]
342+
analysis_time = time.time() - start_time
343+
output_q.put((index,peaks,analysis_time))
316344
else:
317345
raise ValueError(f'Model origin {MODEL_ORIGIN} not available.')
318346

@@ -360,8 +388,12 @@ def get_frames(self) -> tuple:
360388
c_frames, d_maps, i_frames = self._camera_manager.get_frames()
361389
for camera in c_frames:
362390
c_frames[camera] = np.asanyarray(c_frames[camera])
391+
if CROP:
392+
c_frames[camera] = c_frames[camera][CROP_Y[0]:CROP_Y[1],CROP_X[0]:CROP_X[1]].copy()
393+
363394
for camera in i_frames:
364395
i_frames[camera] = np.asanyarray(i_frames[camera])
396+
365397
return c_frames, d_maps, i_frames
366398

367399
def input_frames_for_analysis(self, frames: tuple, index: int):
@@ -379,9 +411,9 @@ def input_frames_for_analysis(self, frames: tuple, index: int):
379411
frame_time = time.time()
380412
self._multiprocessing[camera]['input'].put((index, frame))
381413
if d_maps:
382-
self.store_frames(camera, frame, d_maps[camera], frame_time)
414+
self.store_frames(camera, frame, d_maps[camera], frame_time, index)
383415
else:
384-
self.store_frames(camera, frame, None, frame_time)
416+
self.store_frames(camera, frame, None, frame_time, index)
385417

386418
def get_analysed_frames(self) -> tuple:
387419
"""
@@ -394,19 +426,24 @@ def get_analysed_frames(self) -> tuple:
394426
if self._dlc_running:
395427
analysed_frames = {}
396428
analysis_time = None
397-
frame_width, frame_height = RESOLUTION
429+
430+
if CROP:
431+
frame_height = CROP_Y[1] - CROP_Y[0]
432+
frame_width = CROP_X[1] - CROP_X[0]
433+
else:
434+
frame_width,frame_height = RESOLUTION
435+
398436
for camera in self._multiprocessing:
399437
if self._multiprocessing[camera]['output'].full():
400438
if self._start_time is None:
401439
self._start_time = time.time() # getting the first frame here
402440

403441
# Getting the analysed data
404-
analysed_index, peaks = self._multiprocessing[camera]['output'].get()
442+
analysed_index, peaks, analysis_time = self._multiprocessing[camera]['output'].get()
405443
skeletons = calculate_skeletons(peaks, ANIMALS_NUMBER)
406444
print('', end='\r', flush=True) # this is the line you should not remove
407-
analysed_frame, depth_map, input_time = self.get_stored_frames(camera)
408-
analysis_time = time.time() - input_time
409-
445+
analysed_frame , depth_map, input_time = self.get_stored_frames(camera, analysed_index)
446+
delay_time = time.time() - input_time
410447
# Calculating FPS and plotting the data on frame
411448
self.calculate_fps(analysis_time if analysis_time != 0 else 0.01)
412449
frame_time = time.time() - self._start_time
@@ -419,8 +456,11 @@ def get_analysed_frames(self) -> tuple:
419456
self._experiment_running = False
420457

421458
if self._experiment_running and not self._experiment.experiment_finished:
422-
for skeleton in skeletons:
423-
self._experiment.check_skeleton(analysed_image, skeleton)
459+
if ANIMALS_NUMBER > 1 and not FLATTEN_MA and not PASS_SEPARATE:
460+
self._experiment.check_skeleton(analysed_image,skeletons)
461+
else:
462+
for skeleton in skeletons:
463+
self._experiment.check_skeleton(analysed_image, skeleton)
424464

425465
# Gathering data as pd.Series for output
426466
if self._data_output:
@@ -430,23 +470,30 @@ def get_analysed_frames(self) -> tuple:
430470
analysed_frames[camera] = analysed_image
431471
return analysed_frames, analysis_time
432472

433-
def store_frames(self, camera: str, c_frame, d_map, frame_time: float):
473+
def store_frames(self, camera: str, c_frame, d_map, frame_time: float, index: int):
434474
"""
435-
Store frames currently sent for analysis
475+
Store frames currently sent for analysis in index based dictionary
436476
:param camera: camera name
437477
:param c_frame: color frame
438478
:param d_map: depth map
439479
:param frame_time: inputting time of frameset
480+
:param index: index of frame that is currently analysed
440481
"""
441-
self._stored_frames[camera] = c_frame, d_map, frame_time
482+
if camera in self._stored_frames.keys():
483+
self._stored_frames[camera][index] = c_frame, d_map, frame_time
484+
485+
else:
486+
self._stored_frames[camera] = {}
487+
self._stored_frames[camera][index] = c_frame, d_map, frame_time
442488

443-
def get_stored_frames(self, camera: str):
489+
def get_stored_frames(self, camera: str, index: int):
444490
"""
445-
Retrieve frames currently sent for analysis
491+
Retrieve frames currently sent for analysis, retrieved frames will be removed (popped) from the dictionary
446492
:param camera: camera name
493+
:param index: index of analysed frame
447494
:return:
448495
"""
449-
c_frame, d_map, frame_time = self._stored_frames.get(camera)
496+
c_frame, d_map, frame_time = self._stored_frames[camera].pop(index, None)
450497
return c_frame, d_map, frame_time
451498

452499
def convert_depth_map_to_image(self, d_map):

Readme.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,37 @@
1111

1212
DeepLabStream is a python based multi-purpose tool that enables the realtime tracking and manipulation of animals during ongoing experiments.
1313
Our toolbox was orginally adapted from the previously published [DeepLabCut](https://github.com/AlexEMG/DeepLabCut) ([Mathis et al., 2018](https://www.nature.com/articles/s41593-018-0209-y)) and expanded on its core capabilities, but is now able to utilize a variety of different network architectures for online pose estimation
14-
([DLC + maDLC](https://github.com/AlexEMG/DeepLabCut), [DLC-Live](https://github.com/DeepLabCut/DeepLabCut-live), [DeepPosekit's](https://github.com/jgraving/DeepPoseKit) StackedDenseNet, StackedHourGlass and [LEAP](https://github.com/murthylab/sleap)).
14+
([SLEAP](https://github.com/murthylab/sleap), [DLC-Live](https://github.com/DeepLabCut/DeepLabCut-live), [DeepPosekit's](https://github.com/jgraving/DeepPoseKit) StackedDenseNet, StackedHourGlass and [LEAP](https://github.com/murthylab/sleap)).
15+
1516
DeepLabStreams core feature is the utilization of real-time tracking to orchestrate closed-loop experiments. This can be achieved using any type of camera-based video stream (incl. multiple streams). It enables running experimental protocols that are dependent on a constant stream of bodypart positions and feedback activation of several input/output devices. It's capabilities range from simple region of interest (ROI) based triggers to headdirection or behavior dependent stimulation.
1617

1718
![DLS_Stim](docs/DLSSTim_example.gif)
1819

20+
## New features:
21+
22+
#### 02/2021: Multiple Animal Experiments (Pre-release): Full [SLEAP](https://github.com/murthylab/sleap) integration (Full release coming soon!)
23+
24+
- Updated [Installation](https://github.com/SchwarzNeuroconLab/DeepLabStream/wiki/Installation-&-Testing) (for SLEAP support)
25+
- Single Instance and Multiple Instance models (TopDown & BottomUp) integration (SLEAP v1.1.0a10), more coming soon!
26+
- [New Multiple Animal Experiment Tutorial](https://github.com/SchwarzNeuroconLab/DeepLabStream/wiki/Multiple-Animal-Experiments)
27+
28+
#### 01/2021: DLStream was published in [Communications Biology](https://www.nature.com/articles/s42003-021-01654-9)
29+
30+
#### 12/2021: New pose estimation model integration ([DLC-Live](https://github.com/DeepLabCut/DeepLabCut-live)) and pre-release of further integration ([DeepPosekit's](https://github.com/jgraving/DeepPoseKit) StackedDenseNet, StackedHourGlass and [LEAP](https://github.com/murthylab/sleap))
31+
1932
## Quick Reference:
2033

21-
### Check out or wiki: [DLStream Wiki](https://github.com/SchwarzNeuroconLab/DeepLabStream/wiki)
34+
#### Check out or wiki: [DLStream Wiki](https://github.com/SchwarzNeuroconLab/DeepLabStream/wiki)
2235

23-
### Read the preprint: [Schweihoff et al, 2019](https://doi.org/10.1101/2019.12.20.884478).
36+
#### Read the paper: [Schweihoff, et al. 2021](https://www.nature.com/articles/s42003-021-01654-9)
2437

25-
### Contributing
38+
#### Contributing
2639

2740
If you have feature requests or questions regarding the design of experiments join our [slack group](https://join.slack.com/t/dlstream/shared_invite/zt-jpy2olk1-CuJu0ZylGg_SLbO7zBkcrg)!
2841

2942
We are constantly working to update and increase the capabilities of DLStream.
3043
We welcome all feedback and input from your side.
31-
Also, do not hesitate to contact us for collaborations.
44+
3245

3346
### 1. [Updated Installation & Testing](https://github.com/SchwarzNeuroconLab/DeepLabStream/wiki/Installation-&-Testing)
3447

@@ -49,7 +62,7 @@ Also, do not hesitate to contact us for collaborations.
4962
### 7. [Adapting an existing experiment to your own needs](https://github.com/SchwarzNeuroconLab/DeepLabStream/wiki/Adapting-an-existing-experiment-to-your-own-needs)
5063

5164

52-
65+
5366
### How to use DeepLabStream
5467

5568
Just run
@@ -110,7 +123,12 @@ If you encounter any issues or errors, you can check out the wiki article ([Help
110123

111124
## References:
112125

113-
If you use this code or data please cite [Schweihoff et al, 2019](https://doi.org/10.1101/2019.12.20.884478).
126+
If you use this code or data please cite:
127+
128+
129+
Schweihoff, J.F., Loshakov, M., Pavlova, I. et al. DeepLabStream enables closed-loop behavioral experiments using deep learning-based markerless, real-time posture detection.
130+
131+
Commun Biol 4, 130 (2021). https://doi.org/10.1038/s42003-021-01654-9
114132

115133
## License
116134
This project is licensed under the GNU General Public License v3.0. Note that the software is provided "as is", without warranty of any kind, expressed or implied.

app.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
import cv2
1212

1313
from DeepLabStream import DeepLabStream, show_stream
14+
from utils.generic import MissingFrameError
1415
from utils.configloader import MULTI_CAM, STREAMS, RECORD_EXP
1516
from utils.gui_image import QFrame, ImageWindow, emit_qframes
1617

17-
from PyQt5.QtCore import QThread
18-
from PyQt5.QtWidgets import QPushButton, QApplication, QWidget, QGridLayout
19-
from PyQt5.QtGui import QIcon
20-
18+
from PySide2.QtCore import QThread
19+
from PySide2.QtWidgets import QPushButton, QApplication, QWidget, QGridLayout
20+
from PySide2.QtGui import QIcon
2121

2222
# creating a complete thread process to work in the background
2323
class AThread(QThread):
@@ -42,7 +42,16 @@ def run(self):
4242
Infinite loop with all the streaming, analysis and recording logic
4343
"""
4444
while self.threadactive:
45-
all_frames = stream_manager.get_frames()
45+
try:
46+
all_frames = stream_manager.get_frames()
47+
except MissingFrameError as e:
48+
"""catch missing frame, stop Thread and save what can be saved"""
49+
print(*e.args, '\nShutting down DLStream and saving data...')
50+
stream_manager.finish_streaming()
51+
stream_manager.stop_cameras()
52+
self.stop()
53+
break
54+
4655
color_frames, depth_maps, infra_frames = all_frames
4756

4857
# writing the video

docs/GraphAbstract.png

-17.7 KB
Loading

0 commit comments

Comments
 (0)