Skip to content

Commit 8db430a

Browse files
committed
Merge remote-tracking branch 'origin/master' into dev
# Conflicts: # DeepLabStream.py # settings.ini # utils/configloader.py
2 parents 112a6ab + 629e201 commit 8db430a

File tree

9 files changed

+160
-68
lines changed

9 files changed

+160
-68
lines changed

DeepLabStream.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import click
1919

2020
from utils.configloader import RESOLUTION, FRAMERATE, OUT_DIR, MODEL, MULTI_CAM, STACK_FRAMES, \
21-
ANIMALS_NUMBER, STREAMS, VIDEO, IPWEBCAM
21+
ANIMALS_NUMBER, STREAMS, STREAMING_SOURCE
2222
from utils.poser import load_deeplabcut, get_pose, find_local_peaks_new, calculate_skeletons,\
2323
get_ma_pose, calculate_ma_skeletons, calculate_skeletons_dlc_live
2424
from utils.plotter import plot_bodyparts, plot_metadata_frame
@@ -128,18 +128,17 @@ def set_camera_manager():
128128
:return: the chosen camera manager
129129
"""
130130

131-
if VIDEO:
131+
if STREAMING_SOURCE.lower() == 'video':
132132
from utils.generic import VideoManager
133133
manager = VideoManager()
134134
return manager
135135

136-
elif IPWEBCAM:
136+
elif STREAMING_SOURCE.lower() == 'ipwebcam':
137137
from utils.generic import WebCamManager
138138
manager = WebCamManager()
139139
return manager
140140

141-
142-
else:
141+
elif STREAMING_SOURCE.lower() == 'camera':
143142
manager_list = []
144143
# loading realsense manager, if installed
145144
realsense = find_spec("pyrealsense2") is not None
@@ -175,6 +174,9 @@ def check_for_cameras(camera_manager):
175174
from utils.generic import GenericManager
176175
generic_manager = GenericManager()
177176
return generic_manager
177+
else:
178+
raise ValueError(f'Streaming source {STREAMING_SOURCE} is not a valid option. \n'
179+
f'Please choose from "video", "camera" or "ipwebcam".')
178180

179181
@property
180182
def cameras(self):

experiments/base/experiments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def __init__(self):
289289

290290
#loading settings
291291
self._exp_parameter_dict = dict(TRIGGER ='str',
292-
INTERSTIM_TIME = 'int',
292+
INTERTRIAL_TIME = 'int',
293293
MAX_STIM_TIME = 'int',
294294
MIN_STIM_TIME='int',
295295
MAX_TOTAL_STIM_TIME = 'int',

experiments/custom/experiments.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from functools import partial
1212
from collections import Counter
1313
from experiments.custom.stimulus_process import ClassicProtocolProcess, SimpleProtocolProcess,Timer, ExampleProtocolProcess
14-
from experiments.custom.triggers import ScreenTrigger, RegionTrigger, OutsideTrigger, DirectionTrigger
14+
from experiments.custom.triggers import ScreenTrigger, RegionTrigger, OutsideTrigger, DirectionTrigger, SpeedTrigger
1515
from utils.plotter import plot_triggers_response
1616
from utils.analysis import angle_between_vectors
1717
from experiments.custom.stimulation import show_visual_stim_img,laser_switch
@@ -137,6 +137,80 @@ def get_trial(self):
137137
EXP_COMPLETION = 10
138138

139139

140+
141+
class SpeedExperiment:
142+
"""
143+
Simple class to contain all of the experiment properties
144+
Uses multiprocess to ensure the best possible performance and
145+
to showcase that it is possible to work with any type of equipment, even timer-dependent
146+
"""
147+
def __init__(self):
148+
self.experiment_finished = False
149+
self._threshold = 10
150+
self._event = None
151+
self._current_trial = None
152+
self._event_count = 0
153+
self._trigger = SpeedTrigger(threshold = self._threshold,bodypart= 'tailroot', timewindow_len= 5)
154+
self._exp_timer = Timer(600)
155+
156+
def check_skeleton(self, frame, skeleton):
157+
"""
158+
Checking each passed animal skeleton for a pre-defined set of conditions
159+
Outputting the visual representation, if exist
160+
Advancing trials according to inherent logic of an experiment
161+
:param frame: frame, on which animal skeleton was found
162+
:param skeleton: skeleton, consisting of multiple joints of an animal
163+
"""
164+
self.check_exp_timer() # checking if experiment is still on
165+
166+
if not self.experiment_finished:
167+
result, response = self._trigger.check_skeleton(skeleton=skeleton)
168+
plot_triggers_response(frame, response)
169+
if result:
170+
laser_switch(True)
171+
self._event_count += 1
172+
print(self._event_count)
173+
print('Light on')
174+
175+
else:
176+
laser_switch(False)
177+
print('Light off')
178+
179+
return result, response
180+
181+
182+
def check_exp_timer(self):
183+
"""
184+
Checking the experiment timer
185+
"""
186+
if not self._exp_timer.check_timer():
187+
print("Experiment is finished")
188+
print("Time ran out.")
189+
self.stop_experiment()
190+
191+
def start_experiment(self):
192+
"""
193+
Start the experiment
194+
"""
195+
if not self.experiment_finished:
196+
self._exp_timer.start()
197+
198+
def stop_experiment(self):
199+
"""
200+
Stop the experiment and reset the timer
201+
"""
202+
self.experiment_finished = True
203+
print('Experiment completed!')
204+
self._exp_timer.reset()
205+
# don't forget to stop the laser for safety!
206+
laser_switch(False)
207+
208+
def get_trial(self):
209+
"""
210+
Check which trial is going on right now
211+
"""
212+
return self._current_trial
213+
140214
class FirstExperiment:
141215
def __init__(self):
142216
self.experiment_finished = False

experiments/custom/triggers.py

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
from utils.analysis import angle_between_vectors, calculate_distance, EllipseROI, RectangleROI
1111
from utils.configloader import RESOLUTION
12-
12+
from collections import deque
13+
import numpy as np
1314
"""Single posture triggers"""
1415

1516
class HeaddirectionROITrigger:
@@ -333,15 +334,20 @@ def check_skeleton(self, skeleton: dict):
333334

334335
class FreezeTrigger:
335336
"""
336-
Trigger to check if animal is in freezing state
337+
Trigger to check if animal is moving below a certain speed
337338
"""
338-
def __init__(self, threshold: int, debug: bool = False):
339+
def __init__(self, threshold: int, bodypart: str, timewindow_len:int = 2, debug: bool = False):
339340
"""
340341
Initializing trigger with given threshold
341342
:param threshold: int in pixel how much of a movement does not count
342-
For example threshold of 5 would mean that all movements less then 5 pixels would be ignored
343+
:param bodypart: str of body part in skeleton used for speed calculation
344+
For example threshold of 5 would mean that all movements more then 5 pixels in the last timewindow length frames
345+
would be ignored
343346
"""
347+
self._bodypart = bodypart
344348
self._threshold = threshold
349+
self._timewindow_len = timewindow_len
350+
self._timewindow = deque(maxlen= timewindow_len)
345351
self._skeleton = None
346352
self._debug = debug # not used in this trigger
347353

@@ -354,45 +360,49 @@ def check_skeleton(self, skeleton: dict):
354360
"""
355361
# choosing a point to draw near the skeleton
356362
org_point = skeleton[list(skeleton.keys())[0]]
357-
joint_moved = []
363+
joint_moved = 0
364+
358365
if self._skeleton is None:
359366
result = False
360-
text = 'Not freezing'
367+
text = '...'
361368
self._skeleton = skeleton
362369
else:
363-
for joint in skeleton:
364-
joint_travel = calculate_distance(skeleton[joint], self._skeleton[joint])
365-
joint_moved.append(abs(joint_travel) <= self._threshold)
366-
if all(joint_moved):
370+
joint_travel = calculate_distance(skeleton[self._bodypart], self._skeleton[self._bodypart])
371+
self._timewindow.append(joint_travel)
372+
if len(self._timewindow) == self._timewindow_len:
373+
joint_moved = np.sum(self._timewindow)
374+
375+
if abs(joint_moved) <= self._threshold:
367376
result = True
368377
text = 'Freezing'
369378
else:
370379
result = False
371-
text = 'Not freezing'
372-
self._skeleton = skeleton
373-
380+
text = 'Not Freezing'
381+
self._skeleton = skeleton
374382
color = (0, 255, 0) if result else (0, 0, 255)
375383
response_body = {'plot': {'text': dict(text=text,
376384
org=org_point,
377385
color=color)}}
378386
response = (result, response_body)
379-
return response
380387

388+
return response
381389

382390
class SpeedTrigger:
383391
"""
384392
Trigger to check if animal is moving above a certain speed
385393
"""
386-
def __init__(self, threshold: int, bodypart: str = 'any', debug: bool = False):
394+
def __init__(self, threshold: int, bodypart: str, timewindow_len:int = 2, debug: bool = False):
387395
"""
388396
Initializing trigger with given threshold
389397
:param threshold: int in pixel how much of a movement does not count
390-
:param bodypart: str or list of str, bodypart or list of bodyparts in skeleton to use for trigger,
391-
if "any" will check if any bodypart reaches treshold; default "any"
392-
For example threshold of 5 would mean that all movements less then 5 pixels would be ignored
398+
:param bodypart: str of body part in skeleton used for speed calculation
399+
For example threshold of 5 would mean that all movements less then 5 pixels in the last timewindow length frames
400+
would be ignored
393401
"""
394402
self._bodypart = bodypart
395403
self._threshold = threshold
404+
self._timewindow_len = timewindow_len
405+
self._timewindow = deque(maxlen= timewindow_len)
396406
self._skeleton = None
397407
self._debug = debug # not used in this trigger
398408

@@ -405,36 +415,29 @@ def check_skeleton(self, skeleton: dict):
405415
"""
406416
# choosing a point to draw near the skeleton
407417
org_point = skeleton[list(skeleton.keys())[0]]
408-
joint_moved = []
418+
joint_moved = 0
419+
409420
if self._skeleton is None:
410421
result = False
411-
text = 'First frame'
422+
text = '...'
412423
self._skeleton = skeleton
413424
else:
414-
if self._bodypart is "any":
415-
for joint in skeleton:
416-
joint_travel = calculate_distance(skeleton[joint], self._skeleton[joint])
417-
joint_moved.append(abs(joint_travel) >= self._threshold)
418-
419-
elif isinstance(self._bodypart, list):
420-
for joint in self._bodypart:
421-
joint_travel = calculate_distance(skeleton[joint], self._skeleton[joint])
422-
joint_moved.append(abs(joint_travel) >= self._threshold)
423-
else:
424-
joint_travel = calculate_distance(skeleton[self._bodypart], self._skeleton[self._bodypart])
425-
joint_moved.append(abs(joint_travel) >= self._threshold)
425+
joint_travel = calculate_distance(skeleton[self._bodypart], self._skeleton[self._bodypart])
426+
self._timewindow.append(joint_travel)
427+
if len(self._timewindow) == self._timewindow_len:
428+
joint_moved = np.sum(self._timewindow)
426429

427-
if all(joint_moved):
430+
if abs(joint_moved) >= self._threshold:
428431
result = True
429432
text = 'Running'
430433
else:
431434
result = False
432-
text = ''
433-
self._skeleton = skeleton
434-
435+
text = 'Not Running'
436+
self._skeleton = skeleton
435437
color = (0, 255, 0) if result else (0, 0, 255)
436438
response_body = {'plot': {'text': dict(text=text,
437439
org=org_point,
438440
color=color)}}
439441
response = (result, response_body)
442+
440443
return response

settings.ini

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
[Streaming]
22
RESOLUTION = 320, 256
33
FRAMERATE = 30
4-
STREAMS = color, depth, infrared
54
OUTPUT_DIRECTORY = /Output
6-
MULTIPLE_DEVICES = False
7-
CAMERA_SOURCE = 2
5+
#if you have connected multiple cameras (USB), you will need to select the number OpenCV has given them.
6+
#Default is "0", which takes the first available camera.
7+
CAMERA_SOURCE = 0
8+
#you can use camera, ipwebcam or video to select your input source
9+
STREAMING_SOURCE = camera
810

911
[Pose Estimation]
1012
MODEL_ORIGIN = DLC
@@ -14,10 +16,17 @@ MODEL_NAME = MODEL
1416
ALL_BODYPARTS = bp1, bp2, bp3, bp4
1517

1618
[Experiment]
19+
#Available parameters are "CUSTOM" and "BASE"
1720
EXP_ORIGIN = BASE/CUSTOM
21+
#Name of the experiment config in /experiments/configs or name of the custom experiment in /experiments/custom/experiments.py
1822
EXP_NAME = CONFIG_NAME
23+
#if you want the experiment to be recorded as a raw video set this to "True".
1924
RECORD_EXP = True
2025

2126
[Video]
27+
#Full path to video that you want to use as input. Needs "STREAMING_SOURCE" set to "video"!
2228
VIDEO_SOURCE = PATH_TO_PRERECORDED_VIDEO
23-
VIDEO = False
29+
30+
[IPWEBCAM]
31+
#Standard Port is 5555 if you followed the SmoothStream setup
32+
PORT = 5555

utils/advanced_settings.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#advanced settings only change them if you know what you do!
2+
3+
[Streaming]
4+
STREAMS = color, depth, infrared
5+
MULTIPLE_DEVICES = False
6+
STACK_FRAMES = False
7+
ANIMALS_NUMBER = 1

utils/configloader.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# loading DeepLabStream configuration
1414
# remember when it was called DSC?
1515
dsc_config = cfg.ConfigParser()
16+
adv_dsc_config = cfg.ConfigParser()
1617

1718

1819
def get_script_path():
@@ -23,14 +24,11 @@ def get_script_path():
2324
with open(cfg_path) as cfg_file:
2425
dsc_config.read_file(cfg_file)
2526

26-
27-
#poseestimation
28-
MODEL_ORIGIN = dsc_config['Pose Estimation'].get('MODEL_ORIGIN')
29-
MODEL_PATH = dsc_config['Pose Estimation'].get('MODEL_PATH')
30-
MODEL_NAME = dsc_config['Pose Estimation'].get('MODEL_NAME')
31-
ALL_BODYPARTS = tuple(part for part in dsc_config['Streaming'].get('ALL_BODYPARTS').split(','))
32-
33-
27+
adv_cfg_path = os.path.join(os.path.dirname(__file__), 'advanced_settings.ini')
28+
with open(adv_cfg_path) as adv_cfg_file:
29+
adv_dsc_config.read_file(adv_cfg_file)
30+
# DeepLabCut
31+
deeplabcut_config = dict(dsc_config.items('DeepLabCut'))
3432

3533
# Streaming items
3634
try:
@@ -39,24 +37,16 @@ def get_script_path():
3937
print('Incorrect resolution in config!\n'
4038
'Using default value "RESOLUTION = 848, 480"')
4139
RESOLUTION = (848, 480)
40+
MODEL = dsc_config['Streaming'].get('MODEL')
4241
FRAMERATE = dsc_config['Streaming'].getint('FRAMERATE')
4342
OUT_DIR = dsc_config['Streaming'].get('OUTPUT_DIRECTORY')
44-
STREAM = dsc_config['Streaming'].getboolean('STREAM')
45-
MULTI_CAM = dsc_config['Streaming'].getboolean('MULTIPLE_DEVICES')
46-
STACK_FRAMES = dsc_config['Streaming'].getboolean('STACK_FRAMES') if dsc_config['Streaming'].getboolean(
47-
'STACK_FRAMES') is not None else False
48-
ANIMALS_NUMBER = dsc_config['Streaming'].getint('ANIMALS_NUMBER') if dsc_config['Streaming'].getint(
49-
'ANIMALS_NUMBER') is not None else 1
50-
STREAMS = [str(part).strip() for part in dsc_config['Streaming'].get('STREAMS').split(',')]
5143
CAMERA_SOURCE = dsc_config['Streaming'].get('CAMERA_SOURCE')
52-
44+
STREAMING_SOURCE = dsc_config['Streaming'].get('STREAMING_SOURCE')
5345
# Video
5446
VIDEO_SOURCE = dsc_config['Video'].get('VIDEO_SOURCE')
55-
VIDEO = dsc_config['Video'].getboolean('VIDEO')
5647

5748
#IPWEBCAM
5849
PORT = dsc_config['IPWEBCAM'].get('PORT')
59-
IPWEBCAM = dsc_config['IPWEBCAM'].getboolean('IPWEBCAM')
6050

6151

6252
# experiment
@@ -65,4 +55,12 @@ def get_script_path():
6555
RECORD_EXP = dsc_config['Experiment'].getboolean('RECORD_EXP')
6656

6757
START_TIME = time.time()
68-
EGG = "".join(format(ord(x), 'b') for x in "Hello there!")
58+
59+
60+
"""advanced settings"""
61+
STREAMS = [str(part).strip() for part in adv_dsc_config['Streaming'].get('STREAMS').split(',')]
62+
MULTI_CAM = adv_dsc_config['Streaming'].getboolean('MULTIPLE_DEVICES')
63+
STACK_FRAMES = adv_dsc_config['Streaming'].getboolean('STACK_FRAMES') if adv_dsc_config['Streaming'].getboolean(
64+
'STACK_FRAMES') is not None else False
65+
ANIMALS_NUMBER = adv_dsc_config['Streaming'].getint('ANIMALS_NUMBER') if adv_dsc_config['Streaming'].getint(
66+
'ANIMALS_NUMBER') is not None else 1

0 commit comments

Comments
 (0)