boris-behav-obs 9.1.1__py2.py3-none-any.whl → 9.2__py2.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.
@@ -6,8 +6,8 @@ number of occurences of behaviors
6
6
 
7
7
  import pandas as pd
8
8
 
9
- __version__ = "0.2.0"
10
- __version_date__ = "2025-01-25"
9
+ __version__ = "0.3.0"
10
+ __version_date__ = "2025-03-17"
11
11
  __plugin_name__ = "Number of occurences of behaviors"
12
12
  __author__ = "Olivier Friard - University of Torino - Italy"
13
13
 
@@ -20,43 +20,3 @@ def run(df: pd.DataFrame):
20
20
  df_results: pd.DataFrame = df.groupby(["Subject", "Behavior"])["Behavior"].count().reset_index(name="number of occurences")
21
21
 
22
22
  return df_results
23
-
24
-
25
- def main(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
26
- """
27
- filter by selected observations.
28
- filter by selected subjects.
29
- filter by selected behaviors.
30
- filter by time interval.
31
- """
32
-
33
- # filter selected observations
34
- if observations_list:
35
- df = df[df["Observation id"].isin(observations_list)]
36
-
37
- if parameters:
38
- # filter selected subjects
39
- df = df[df["Subject"].isin(parameters["selected subjects"])]
40
-
41
- # filter selected behaviors
42
- df = df[df["Behavior"].isin(parameters["selected behaviors"])]
43
-
44
- # filter selected time interval
45
- if parameters["start time"] is not None and parameters["end time"] is not None:
46
- MIN_TIME = parameters["start time"]
47
- MAX_TIME = parameters["end time"]
48
- df_interval = df[
49
- (
50
- ((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
51
- | ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
52
- )
53
- | ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
54
- ]
55
- df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
56
- df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
57
-
58
- df_interval.loc[:, "Duration (s)"] = df_interval["Stop (s)"] - df_interval["Start (s)"]
59
-
60
- df = df_interval
61
-
62
- return run(df)
@@ -6,8 +6,8 @@ number of occurences of behaviors by independent_variable
6
6
 
7
7
  import pandas as pd
8
8
 
9
- __version__ = "0.2.0"
10
- __version_date__ = "2025-01-25"
9
+ __version__ = "0.3.0"
10
+ __version_date__ = "2025-03-17"
11
11
  __plugin_name__ = "Number of occurences of behaviors by subject by independent_variable"
12
12
  __author__ = "Olivier Friard - University of Torino - Italy"
13
13
 
@@ -15,9 +15,9 @@ __author__ = "Olivier Friard - University of Torino - Italy"
15
15
  def run(df: pd.DataFrame):
16
16
  """
17
17
  Calculate the number of occurrences of behaviors by subject and by independent_variable.
18
- """
19
18
 
20
- str_results: str = ""
19
+ This plugin returns a Pandas dataframe
20
+ """
21
21
 
22
22
  df_results: df.DataFrame = (
23
23
  df.groupby(
@@ -31,44 +31,4 @@ def run(df: pd.DataFrame):
31
31
  .reset_index(name="number of occurences")
32
32
  )
33
33
 
34
- return df_results, str_results
35
-
36
-
37
- def main(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
38
- """
39
- filter by selected observations.
40
- filter by selected subjects.
41
- filter by selected behaviors.
42
- filter by time interval.
43
- """
44
-
45
- # filter selected observations
46
- if observations_list:
47
- df = df[df["Observation id"].isin(observations_list)]
48
-
49
- if parameters:
50
- # filter selected subjects
51
- df = df[df["Subject"].isin(parameters["selected subjects"])]
52
-
53
- # filter selected behaviors
54
- df = df[df["Behavior"].isin(parameters["selected behaviors"])]
55
-
56
- # filter selected time interval
57
- if parameters["start time"] is not None and parameters["end time"] is not None:
58
- MIN_TIME = parameters["start time"]
59
- MAX_TIME = parameters["end time"]
60
- df_interval = df[
61
- (
62
- ((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
63
- | ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
64
- )
65
- | ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
66
- ]
67
- df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
68
- df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
69
-
70
- df_interval.loc[:, "Duration (s)"] = df_interval["Stop (s)"] - df_interval["Start (s)"]
71
-
72
- df = df_interval
73
-
74
- return run(df)
34
+ return df_results
@@ -7,8 +7,8 @@ Time budget
7
7
  import pandas as pd
8
8
  import numpy as np
9
9
 
10
- __version__ = "0.2.0"
11
- __version_date__ = "2025-01-25"
10
+ __version__ = "0.3.0"
11
+ __version_date__ = "2025-03-17"
12
12
  __plugin_name__ = "Time budget"
13
13
  __author__ = "Olivier Friard - University of Torino - Italy"
14
14
 
@@ -18,7 +18,7 @@ def run(df: pd.DataFrame):
18
18
  Calculate the following values:
19
19
 
20
20
  - Total number of occurences of behavior
21
- - Total duration of behavior (in seconds)
21
+ - Total duration of behavior (in seconds) (pandas.DataFrame.sum() ignore NaN values when computing the sum. Use min_count=1)
22
22
  - Duration mean of behavior (in seconds)
23
23
  - Standard deviation of behavior duration (in seconds)
24
24
  - Inter-event intervals mean (in seconds)
@@ -26,11 +26,15 @@ def run(df: pd.DataFrame):
26
26
  - % of total subject observation duration
27
27
  """
28
28
 
29
+ print("running time budget plugin")
30
+
31
+ print(df)
32
+
29
33
  group_by = ["Subject", "Behavior"]
30
34
 
31
35
  dfs = [
32
36
  df.groupby(group_by)["Behavior"].count().reset_index(name="number of occurences"),
33
- df.groupby(group_by)["Duration (s)"].sum().reset_index(name="total duration"),
37
+ df.groupby(group_by)["Duration (s)"].sum(min_count=1).reset_index(name="total duration"),
34
38
  df.groupby(group_by)["Duration (s)"].mean().astype(float).round(3).reset_index(name="duration mean"),
35
39
  df.groupby(group_by)["Duration (s)"].std().astype(float).round(3).reset_index(name="duration std dev"),
36
40
  ]
@@ -48,7 +52,7 @@ def run(df: pd.DataFrame):
48
52
  interval = (df.groupby(["Subject"])["Stop (s)"].max() - df.groupby(["Subject"])["Start (s)"].min()).replace(0, np.nan)
49
53
 
50
54
  dfs.append(
51
- (100 * df.groupby(group_by)["Duration (s)"].sum() / interval)
55
+ (100 * df.groupby(group_by)["Duration (s)"].sum(min_count=1) / interval)
52
56
  .astype(float)
53
57
  .round(3)
54
58
  .reset_index(name="% of total subject observation duration")
@@ -59,45 +63,3 @@ def run(df: pd.DataFrame):
59
63
  merged_df = pd.merge(merged_df, df, on=group_by)
60
64
 
61
65
  return merged_df
62
-
63
-
64
- def main(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
65
- """
66
- filter by selected observations.
67
- filter by selected subjects.
68
- filter by selected behaviors.
69
- filter by time interval.
70
- """
71
-
72
- # filter selected observations
73
- if observations_list:
74
- df = df[df["Observation id"].isin(observations_list)]
75
-
76
- if parameters:
77
- # filter selected subjects
78
- df = df[df["Subject"].isin(parameters["selected subjects"])]
79
-
80
- # filter selected behaviors
81
- df = df[df["Behavior"].isin(parameters["selected behaviors"])]
82
-
83
- # filter selected time interval
84
- if parameters["start time"] is not None and parameters["end time"] is not None:
85
- MIN_TIME = parameters["start time"]
86
- MAX_TIME = parameters["end time"]
87
-
88
- df_interval = df[
89
- (
90
- ((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
91
- | ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
92
- )
93
- | ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
94
- ]
95
-
96
- df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
97
- df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
98
-
99
- df_interval.loc[:, "Duration (s)"] = df_interval["Stop (s)"] - df_interval["Start (s)"]
100
-
101
- df = df_interval
102
-
103
- return run(df)
boris/cooccurence.py CHANGED
@@ -98,6 +98,7 @@ def get_cooccurence(self):
98
98
 
99
99
  start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
100
100
 
101
+ # loop on choose subjects /behaviors until parameters are OK
101
102
  while True:
102
103
  flag_ok: bool = True
103
104
  parameters = select_subj_behav.choose_obs_subj_behav_category(
@@ -113,7 +114,7 @@ def get_cooccurence(self):
113
114
  show_exclude_non_coded_behaviors=True,
114
115
  )
115
116
 
116
- if parameters == {}: # cancel button pressed
117
+ if not parameters: # cancel button pressed
117
118
  return
118
119
 
119
120
  if not parameters[cfg.SELECTED_SUBJECTS]:
boris/core.py CHANGED
@@ -34,7 +34,6 @@ import json
34
34
  import logging
35
35
  import pathlib as pl
36
36
  import platform
37
- import importlib
38
37
  import re
39
38
  import PIL.Image
40
39
  import PIL.ImageEnhance
@@ -52,7 +51,6 @@ from decimal import Decimal as dec
52
51
  from decimal import ROUND_DOWN
53
52
  import gzip
54
53
  from collections import deque
55
- import pandas as pd
56
54
  import matplotlib
57
55
  import zipfile
58
56
  import shutil
@@ -129,7 +127,6 @@ from . import config_file
129
127
  from . import select_subj_behav
130
128
  from . import observation_operations
131
129
  from . import write_event
132
- from . import view_df
133
130
 
134
131
 
135
132
  # matplotlib.pyplot.switch_backend("Qt5Agg")
@@ -5762,6 +5759,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5762
5759
  self.extract_frame(self.dw_player[0])
5763
5760
 
5764
5761
  def obs_param(self):
5762
+ """
5763
+ allow user to select observations and then subjects and behaviors
5764
+ """
5765
5765
  _, selected_observations = select_observations.select_observations2(self, mode=cfg.MULTIPLE, windows_title="")
5766
5766
 
5767
5767
  if not selected_observations:
@@ -5786,8 +5786,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5786
5786
 
5787
5787
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
5788
5788
 
5789
+ print(f"{start_coding=}")
5790
+ print(f"{end_coding=}")
5791
+
5789
5792
  start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
5790
5793
 
5794
+ print(f"{start_interval=}")
5795
+ print(f"{end_interval=}")
5796
+
5791
5797
  parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
5792
5798
  self,
5793
5799
  selected_observations,
@@ -5803,6 +5809,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5803
5809
  if parameters == {}:
5804
5810
  return [], {}
5805
5811
 
5812
+ print(f"{parameters=}")
5813
+
5806
5814
  if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
5807
5815
  QMessageBox.warning(None, cfg.programName, "Select subject(s) and behavior(s) to analyze")
5808
5816
  return [], {}
@@ -5810,94 +5818,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5810
5818
  logging.debug(f"{parameters=}")
5811
5819
  return selected_observations, parameters
5812
5820
 
5813
- def run_plugin(self):
5814
- """
5815
- run plugin
5816
- """
5817
- if not self.project:
5818
- QMessageBox.warning(
5819
- self,
5820
- cfg.programName,
5821
- "No observations found. Open a project first",
5822
- QMessageBox.Ok | QMessageBox.Default,
5823
- QMessageBox.NoButton,
5824
- )
5825
- return
5826
-
5827
- logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
5828
-
5829
- plugin_name = self.sender().text()
5830
- if plugin_name not in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
5831
- QMessageBox.critical(self, cfg.programName, f"Plugin '{plugin_name}' not found")
5832
- return
5833
-
5834
- plugin_path = self.config_param.get(cfg.ANALYSIS_PLUGINS, {})[plugin_name]
5835
-
5836
- logging.debug(f"{plugin_path=}")
5837
-
5838
- if not pl.Path(plugin_path).is_file():
5839
- QMessageBox.critical(self, cfg.programName, f"The plugin {plugin_path} was not found.")
5840
- return
5841
-
5842
- logging.debug(f"run plugin from {plugin_path}")
5843
-
5844
- module_name = pl.Path(plugin_path).stem
5845
-
5846
- spec = importlib.util.spec_from_file_location(module_name, plugin_path)
5847
- plugin_module = importlib.util.module_from_spec(spec)
5848
-
5849
- logging.debug(f"{plugin_module=}")
5850
-
5851
- spec.loader.exec_module(plugin_module)
5852
-
5853
- logging.info(
5854
- f"{plugin_module.__plugin_name__} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}"
5855
- )
5856
-
5857
- selected_observations, parameters = self.obs_param()
5858
- if not selected_observations:
5859
- return
5860
-
5861
- df = project_functions.project2dataframe(self.pj, selected_observations)
5862
-
5863
- logging.debug("dataframe info")
5864
- logging.debug(f"{df.info()}")
5865
- logging.debug(f"{df.head()}")
5866
-
5867
- # df_results, str_results = plugin_module.main(df, observations_list=selected_observations, parameters=parameters)
5868
-
5869
- plugin_results = plugin_module.main(df, observations_list=selected_observations, parameters=parameters)
5870
- # test if tuple: if not transform to tuple
5871
- if not isinstance(plugin_results, tuple):
5872
- plugin_results = tuple([plugin_results])
5873
-
5874
- self.plugin_visu: list = []
5875
- for result in plugin_results:
5876
- if isinstance(result, str):
5877
- self.plugin_visu.append(dialog.Results_dialog())
5878
- self.plugin_visu[-1].setWindowTitle(self.sender().text())
5879
- self.plugin_visu[-1].ptText.clear()
5880
- self.plugin_visu[-1].ptText.appendPlainText(result)
5881
- self.plugin_visu[-1].show()
5882
- elif isinstance(result, pd.DataFrame):
5883
- self.plugin_visu.append(
5884
- view_df.View_df(self.sender().text(), f"{plugin_module.__version__} ({plugin_module.__version_date__})", result)
5885
- )
5886
- self.plugin_visu[-1].show()
5887
- else:
5888
- # result is not str nor dataframe
5889
- QMessageBox.critical(
5890
- None,
5891
- cfg.programName,
5892
- (
5893
- f"Plugin returns an unknown object type: {type(result)}\n\n"
5894
- "Plugins must return str and/or Pandas Dataframes.\n"
5895
- "Check the plugin code."
5896
- ),
5897
- QMessageBox.Ok | QMessageBox.Default,
5898
- QMessageBox.NoButton,
5899
- )
5900
-
5901
5821
 
5902
5822
  def main():
5903
5823
  # QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
@@ -43,9 +43,8 @@ from PySide6.QtWidgets import (
43
43
  QSlider,
44
44
  QMainWindow,
45
45
  QDockWidget,
46
- QPushButton,
47
46
  )
48
- from PySide6.QtCore import Qt, QDateTime, QTimer, QObject, QEvent
47
+ from PySide6.QtCore import Qt, QDateTime, QTimer
49
48
  from PySide6.QtGui import QFont, QIcon, QTextCursor
50
49
 
51
50
  from PySide6 import QtTest
@@ -365,8 +364,9 @@ def time_intervals_range(observations: dict, observations_list: list) -> Tuple[O
365
364
  for obs_id in observations_list:
366
365
  observation = observations[obs_id]
367
366
  offset = observation[cfg.TIME_OFFSET]
368
- start_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset)
369
- end_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset)
367
+ if dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset and dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset:
368
+ start_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset)
369
+ end_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset)
370
370
 
371
371
  if not start_interval_list:
372
372
  earliest_start_interval = None
@@ -376,7 +376,7 @@ def time_intervals_range(observations: dict, observations_list: list) -> Tuple[O
376
376
  if not end_interval_list:
377
377
  latest_end_interval = None
378
378
  else:
379
- latest_end_interval = min([x for x in end_interval_list])
379
+ latest_end_interval = max([x for x in end_interval_list])
380
380
 
381
381
  return earliest_start_interval, latest_end_interval
382
382
 
boris/param_panel.py CHANGED
@@ -95,7 +95,13 @@ class Param_panel(QDialog, Ui_Dialog):
95
95
  if not ((self.start_interval is None) or self.start_interval.is_nan()):
96
96
  # Set start_time and end_time widgets values even if it is not shown with
97
97
  # more than 1 observation as some analyses might use it (eg: advanced event filtering)
98
+
99
+ print(f"{self.end_interval=}")
100
+
98
101
  end_interval = self.end_interval if self.end_interval != 0 else self.media_duration
102
+
103
+ print(f"{end_interval=}")
104
+
99
105
  self.start_time.set_time(self.start_interval)
100
106
  self.end_time.set_time(end_interval)
101
107
  self.frm_time_interval.setEnabled(False)
boris/plugins.py CHANGED
@@ -19,10 +19,19 @@ Copyright 2012-2025 Olivier Friard
19
19
  MA 02110-1301, USA.
20
20
  """
21
21
 
22
+ import importlib
22
23
  import logging
24
+ import numpy as np
25
+ import pandas as pd
26
+ from pathlib import Path
27
+
23
28
  from PySide6.QtGui import QAction
29
+ from PySide6.QtWidgets import QMessageBox
30
+
24
31
  from . import config as cfg
25
- from pathlib import Path
32
+ from . import project_functions
33
+ from . import dialog
34
+ from . import view_df
26
35
 
27
36
 
28
37
  def add_plugins_to_menu(self):
@@ -32,7 +41,7 @@ def add_plugins_to_menu(self):
32
41
  for plugin_name in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
33
42
  logging.debug(f"adding plugin '{plugin_name}' to menu")
34
43
  # Create an action for each submenu option
35
- action = QAction(self, triggered=self.run_plugin)
44
+ action = QAction(self, triggered=lambda checked=False, name=plugin_name: run_plugin(self, name))
36
45
  action.setText(plugin_name)
37
46
 
38
47
  self.menu_plugins.addAction(action)
@@ -77,3 +86,162 @@ def load_plugins(self):
77
86
  self.config_param[cfg.ANALYSIS_PLUGINS][plugin_name] = str(file_)
78
87
 
79
88
  logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
89
+
90
+
91
+ def plugin_df_filter(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
92
+ """
93
+ filter the dataframe following parameters
94
+
95
+ filter by selected observations.
96
+ filter by selected subjects.
97
+ filter by selected behaviors.
98
+ filter by time interval.
99
+ """
100
+
101
+ # filter selected observations
102
+ df = df[df["Observation id"].isin(observations_list)]
103
+
104
+ if parameters:
105
+ # filter selected subjects
106
+ df = df[df["Subject"].isin(parameters["selected subjects"])]
107
+
108
+ # filter selected behaviors
109
+ df = df[df["Behavior"].isin(parameters["selected behaviors"])]
110
+
111
+ if parameters["time"] == "interval of observation":
112
+ # filter each observation with observation interval start/stop
113
+
114
+ # keep events between observation interval start time and observation interval stop/end
115
+ df_interval = df[
116
+ (
117
+ ((df["Start (s)"] >= df["Observation interval start"]) & (df["Start (s)"] <= df["Observation interval stop"]))
118
+ | ((df["Stop (s)"] >= df["Observation interval start"]) & (df["Stop (s)"] <= df["Observation interval stop"]))
119
+ )
120
+ | ((df["Start (s)"] < df["Observation interval start"]) & (df["Stop (s)"] > df["Observation interval stop"]))
121
+ ]
122
+
123
+ df_interval.loc[df["Start (s)"] < df["Observation interval start"], "Start (s)"] = df["Observation interval start"]
124
+ df_interval.loc[df["Stop (s)"] > df["Observation interval stop"], "Stop (s)"] = df["Observation interval stop"]
125
+
126
+ df_interval.loc[:, "Duration (s)"] = (df_interval["Stop (s)"] - df_interval["Start (s)"]).replace(0, np.nan)
127
+
128
+ df = df_interval
129
+
130
+ else:
131
+ # filter selected time interval
132
+ if parameters["start time"] is not None and parameters["end time"] is not None:
133
+ MIN_TIME = parameters["start time"]
134
+ MAX_TIME = parameters["end time"]
135
+
136
+ # keep events between start time and end_time
137
+ df_interval = df[
138
+ (
139
+ ((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
140
+ | ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
141
+ )
142
+ | ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
143
+ ]
144
+
145
+ df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
146
+ df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
147
+
148
+ df_interval.loc[:, "Duration (s)"] = (df_interval["Stop (s)"] - df_interval["Start (s)"]).replace(0, np.nan)
149
+
150
+ df = df_interval
151
+
152
+ print("filtered")
153
+ print("=" * 50)
154
+
155
+ print(f"{df=}")
156
+
157
+ return df
158
+
159
+
160
+ def run_plugin(self, plugin_name):
161
+ """
162
+ run plugin
163
+ """
164
+
165
+ if not self.project:
166
+ QMessageBox.warning(
167
+ self,
168
+ cfg.programName,
169
+ "No observations found. Open a project first",
170
+ QMessageBox.Ok | QMessageBox.Default,
171
+ QMessageBox.NoButton,
172
+ )
173
+ return
174
+
175
+ logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
176
+
177
+ if plugin_name not in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
178
+ QMessageBox.critical(self, cfg.programName, f"Plugin '{plugin_name}' not found")
179
+ return
180
+
181
+ plugin_path = self.config_param.get(cfg.ANALYSIS_PLUGINS, {})[plugin_name]
182
+
183
+ logging.debug(f"{plugin_path=}")
184
+
185
+ if not Path(plugin_path).is_file():
186
+ QMessageBox.critical(self, cfg.programName, f"The plugin {plugin_path} was not found.")
187
+ return
188
+
189
+ logging.debug(f"run plugin from {plugin_path}")
190
+
191
+ module_name = Path(plugin_path).stem
192
+
193
+ spec = importlib.util.spec_from_file_location(module_name, plugin_path)
194
+ plugin_module = importlib.util.module_from_spec(spec)
195
+
196
+ logging.debug(f"{plugin_module=}")
197
+
198
+ spec.loader.exec_module(plugin_module)
199
+
200
+ logging.info(
201
+ f"{plugin_module.__plugin_name__} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}"
202
+ )
203
+
204
+ selected_observations, parameters = self.obs_param()
205
+ if not selected_observations:
206
+ return
207
+
208
+ df = project_functions.project2dataframe(self.pj, selected_observations)
209
+
210
+ """
211
+ logging.debug("dataframe info")
212
+ logging.debug(f"{df.info()}")
213
+ logging.debug(f"{df.head()}")
214
+ """
215
+
216
+ # filter the dataframe with parameters
217
+ filtered_df = plugin_df_filter(df, observations_list=selected_observations, parameters=parameters)
218
+
219
+ plugin_results = plugin_module.run(filtered_df)
220
+ # test if plugin_tests is a tuple: if not transform to tuple
221
+ if not isinstance(plugin_results, tuple):
222
+ plugin_results = tuple([plugin_results])
223
+
224
+ self.plugin_visu: list = []
225
+ for result in plugin_results:
226
+ if isinstance(result, str):
227
+ self.plugin_visu.append(dialog.Results_dialog())
228
+ self.plugin_visu[-1].setWindowTitle(plugin_name)
229
+ self.plugin_visu[-1].ptText.clear()
230
+ self.plugin_visu[-1].ptText.appendPlainText(result)
231
+ self.plugin_visu[-1].show()
232
+ elif isinstance(result, pd.DataFrame):
233
+ self.plugin_visu.append(view_df.View_df(plugin_name, f"{plugin_module.__version__} ({plugin_module.__version_date__})", result))
234
+ self.plugin_visu[-1].show()
235
+ else:
236
+ # result is not str nor dataframe
237
+ QMessageBox.critical(
238
+ None,
239
+ cfg.programName,
240
+ (
241
+ f"Plugin returns an unknown object type: {type(result)}\n\n"
242
+ "Plugins must return str and/or Pandas Dataframes.\n"
243
+ "Check the plugin code."
244
+ ),
245
+ QMessageBox.Ok | QMessageBox.Default,
246
+ QMessageBox.NoButton,
247
+ )
@@ -39,7 +39,6 @@ from . import db_functions
39
39
  from . import dialog
40
40
  from . import observation_operations
41
41
  from . import portion as I
42
- from . import project_functions
43
42
  from . import utilities as util
44
43
  from . import version
45
44
 
@@ -1806,9 +1805,11 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1806
1805
 
1807
1806
  data = {
1808
1807
  "Observation id": [],
1809
- # "Observation date": [],
1810
- # "Description": [],
1811
- # "Observation type": [],
1808
+ "Observation date": [],
1809
+ "Description": [],
1810
+ "Observation type": [],
1811
+ "Observation interval start": [],
1812
+ "Observation interval stop": [],
1812
1813
  # "Source": [],
1813
1814
  # "Time offset (s)": [],
1814
1815
  # "Coding duration": [],
@@ -1847,9 +1848,11 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1847
1848
 
1848
1849
  type_ = {
1849
1850
  "Observation id": "string",
1850
- # "Observation date": "string",
1851
- # "Description": "string",
1852
- # "Observation type": "string",
1851
+ "Observation date": "string",
1852
+ "Description": "string",
1853
+ "Observation type": "string",
1854
+ "Observation interval start": "float64",
1855
+ "Observation interval stop": "float64",
1853
1856
  # "Source": "string",
1854
1857
  # "Time offset (s)": "string",
1855
1858
  # "Coding duration": "float64",
@@ -1885,14 +1888,6 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1885
1888
  "Comment stop": "string",
1886
1889
  }
1887
1890
 
1888
- """
1889
- state_behaviors = [
1890
- pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
1891
- for x in pj[cfg.ETHOGRAM]
1892
- if pj[cfg.ETHOGRAM][x]["type"] in (cfg.STATE_EVENT, cfg.STATE_EVENT_WITH_CODING_MAP)
1893
- ]
1894
- """
1895
-
1896
1891
  state_behaviors = util.state_behavior_codes(pj[cfg.ETHOGRAM])
1897
1892
 
1898
1893
  for obs_id in pj[cfg.OBSERVATIONS]:
@@ -1904,9 +1899,21 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1904
1899
  if idx_event in stop_event_idx:
1905
1900
  continue
1906
1901
  data["Observation id"].append(obs_id)
1907
- # data["Observation date"].append(pj["observations"][obs_id]["date"])
1908
- # data["Description"].append(pj["observations"][obs_id]["description"])
1909
- # data["Observation type"].append(pj["observations"][obs_id]["type"])
1902
+ data["Observation date"].append(pj["observations"][obs_id]["date"])
1903
+ data["Description"].append(" ".join(pj["observations"][obs_id]["description"].splitlines()))
1904
+ data["Observation type"].append(pj["observations"][obs_id]["type"])
1905
+
1906
+ data["Observation interval start"].append(
1907
+ pj["observations"][obs_id]["observation time interval"][0]
1908
+ if pj["observations"][obs_id]["observation time interval"][0]
1909
+ else None
1910
+ )
1911
+ data["Observation interval stop"].append(
1912
+ pj["observations"][obs_id]["observation time interval"][1]
1913
+ if pj["observations"][obs_id]["observation time interval"][1]
1914
+ else None
1915
+ )
1916
+
1910
1917
  # data["Source"].append("")
1911
1918
  # data["Time offset (s)"].append(pj["observations"][obs_id]["time offset"])
1912
1919
  # data["Coding duration"].append("")
@@ -1932,7 +1939,7 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1932
1939
  data[modifier_set].append(np.nan)
1933
1940
 
1934
1941
  data["Behavior type"].append(cfg.STATE_EVENT if event[2] in state_behaviors else cfg.POINT_EVENT)
1935
- data["Start (s)"].append(event[0])
1942
+ data["Start (s)"].append(float(event[0]))
1936
1943
  if event[2] in state_behaviors:
1937
1944
  # search stop
1938
1945
  # print(f"==> {idx_event=} {event[1:4]=}")
@@ -1941,8 +1948,8 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1941
1948
  if event2[1:4] == event[1:4]:
1942
1949
  # print("found")
1943
1950
  stop_event_idx.add(idx_event2)
1944
- data["Stop (s)"].append(event2[0])
1945
- data["Duration (s)"].append(event2[0] - event[0])
1951
+ data["Stop (s)"].append(float(event2[0]))
1952
+ data["Duration (s)"].append(float(event2[0] - event[0]))
1946
1953
  data["Comment start"].append(event[4])
1947
1954
  data["Comment stop"].append(event2[4])
1948
1955
  break
@@ -1950,9 +1957,17 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1950
1957
  raise ("not paired")
1951
1958
 
1952
1959
  else: # point
1953
- data["Stop (s)"].append(event[0])
1960
+ data["Stop (s)"].append(float(event[0]))
1954
1961
  data["Duration (s)"].append(np.nan)
1955
1962
  data["Comment start"].append(event[4])
1956
1963
  data["Comment stop"].append(event[4])
1957
1964
 
1965
+ # Set the display option to show all rows and columns
1966
+ pd.set_option("display.max_rows", None)
1967
+ pd.set_option("display.max_columns", None)
1968
+
1969
+ pd.DataFrame(data).info()
1970
+
1971
+ print(pd.DataFrame(data))
1972
+
1958
1973
  return pd.DataFrame(data)
@@ -123,6 +123,10 @@ def choose_obs_subj_behav_category(
123
123
  paramPanelWindow.start_time.set_time(start_coding)
124
124
  paramPanelWindow.end_time.set_time(end_coding)
125
125
 
126
+ # check observation time interval
127
+ if start_interval is None or start_interval.is_nan() or end_interval is None or end_interval.is_nan():
128
+ paramPanelWindow.rb_obs_interval.setEnabled(False)
129
+
126
130
  if selected_observations:
127
131
  observedSubjects = project_functions.extract_observed_subjects(self.pj, selected_observations)
128
132
  else:
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.1.1"
24
- __version_date__ = "2025-03-12"
23
+ __version__ = "9.2"
24
+ __version_date__ = "2025-03-17"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: boris-behav-obs
3
- Version: 9.1.1
3
+ Version: 9.2
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -15,8 +15,8 @@ boris/config_file.py,sha256=1-2ZmTvKET57rwrLR1dXblt0AxMpGB1LAiHxu-Sy8es,13543
15
15
  boris/connections.py,sha256=y_KkIj3WQQEepJASATHBpZaqwT-ERyFNovy2Q8e7FAA,19321
16
16
  boris/converters.py,sha256=c1Jps-URoglY5ILQHz-pCCf6-4DFUHZLtqr_ofsrFg0,11722
17
17
  boris/converters_ui.py,sha256=uu7LOBV_fKv2DBdOqsqPwjGsjgONr5ODBoscAA-EP48,9900
18
- boris/cooccurence.py,sha256=NV3lPhzKptyYh_pSjx1a_FqwaKstqIwj46GpNcyJ4aY,10236
19
- boris/core.py,sha256=ncIjy9W5jDKkdmytTNGwq2qscZVlZ0tMd9bOOro_9dg,237708
18
+ boris/cooccurence.py,sha256=V3HZejWsH-BkCDMCFSRkQ5a75Ne-4elvI3RTxY5YCBk,10299
19
+ boris/core.py,sha256=1T60p9giYcBX5DO1cygsyj-7hIpdUqqIMTZb1jsddl4,234461
20
20
  boris/core_qrc.py,sha256=T3ki5e2Pj0I0QBGz63MPUgZzl8F_VHZwSq074mRNBDU,650669
21
21
  boris/core_ui.py,sha256=SeC26uveDCjrCBLsRPuQ6FaapKfON_HIRcQStEDLhl4,76384
22
22
  boris/db_functions.py,sha256=Uw9wWH_Pe-qNzpV1k21YG_jKsoOmfY_iiK_7ARZHGDc,13352
@@ -47,11 +47,11 @@ boris/mpv-1.0.3.py,sha256=EXRtzQqFjOn4wMC6482Ilq3fNQ9N1GRP1VxwLzdeaBY,88077
47
47
  boris/mpv.py,sha256=EfzIHjPbgewG4w3smEtqEUPZoVwYmMQkL4Q8ZyW-a58,76410
48
48
  boris/mpv2.py,sha256=IUI4t4r9GYX7G5OXTjd3RhMMOkDdfal_15buBgksLsk,92152
49
49
  boris/observation.py,sha256=oop08nflDLZAgDbIB8GOiVdTgLhppJ_ODH0Z24cyqvE,57176
50
- boris/observation_operations.py,sha256=w295Vlj6qparn7xcdh1tkwgXXx2QTnq9J6KUJGh1zvg,105533
50
+ boris/observation_operations.py,sha256=8rVC_iWb0Y8zkd64_XvDxD2e7_jsKdGfDSdyi4eDRAs,105643
51
51
  boris/observation_ui.py,sha256=DAnU94QBNvkLuHT6AxTwqSk_D_n6VUhSl8PexZv_dUk,33309
52
52
  boris/observations_list.py,sha256=NqwECGHtHYmKhSe-qCfqPmJ24SSfzlXvIXS2i3op_zE,10591
53
53
  boris/otx_parser.py,sha256=70QvilzFHXbjAHR88YH0aEXJ3xxheLS5fZGgHFHGpNE,16367
54
- boris/param_panel.py,sha256=Sna2SlJt1CSUEE_bNX2X6ibFjs2Um0-ZjtQf5-JlJTc,8693
54
+ boris/param_panel.py,sha256=G0XzNmJIX89-n2OQTDccuY_wWMhr3p7GB4ZorbU6EWc,8786
55
55
  boris/param_panel_ui.py,sha256=4emQDFmuL4_R7bKxosLjdUb-VSPWkDm7suy38F5EKcA,13260
56
56
  boris/player_dock_widget.py,sha256=VBuuGoAb2D027oFnMBlIQPiSQcEdedhoQltNFSJq8mw,5982
57
57
  boris/plot_data_module.py,sha256=6QbLKfyGp4TYRyHnB9G45y5XrpeXLytcorltEAWfYak,16562
@@ -59,18 +59,18 @@ boris/plot_events.py,sha256=hA_Yt-ZaxbAOKjN7gYHiaEx5TPR-7HIWGJXXg4cq6lw,24048
59
59
  boris/plot_events_rt.py,sha256=xig__Uea3mQqO5raMBVB3pm3vuQkjAbJpwSS7AwIob8,8327
60
60
  boris/plot_spectrogram_rt.py,sha256=JV8N7T8133wGVhlPxmgOb426u1g1p21-LbTqgaeddkk,8361
61
61
  boris/plot_waveform_rt.py,sha256=05JN_6HCq674ROore_6PNw93GQNZJQDlDxp2ODAFkkA,7474
62
- boris/plugins.py,sha256=2RyrS3fdJ2lA8FhG_qymDCfaFyzK3TaRy2ll3yHjlz4,2817
62
+ boris/plugins.py,sha256=RtC_Ew6VxAwWQqRuKQj9FgpsSBGoZmZuCZ5gPj0EiwI,8963
63
63
  boris/preferences.py,sha256=qPfd9Tyg7u30kXwVqMOgkdy2RXri9bItRa5U2-ZVQmg,16847
64
64
  boris/preferences_ui.py,sha256=D2bFLb3E0m6IwSeqKoItRDiqvPmJGoeXLHF2K02n1Zo,26293
65
65
  boris/project.py,sha256=hAeAb5pD0u_l0bezU9ePvbTOYQKfxrFGvYB2NAqSDHg,84377
66
- boris/project_functions.py,sha256=Bu-HD58uYL__v2ih5OhQnVF22_lPrnvX3LCw4wheC1I,79251
66
+ boris/project_functions.py,sha256=p_CC2UoFEbb0PpKt9c5MtJulSszz-fV1o1sgFRFVQEk,79920
67
67
  boris/project_import_export.py,sha256=1FdsYFzZ_jrhPRaH7xEkcPnh-hQXN4HFz1PhsIsSoL8,38361
68
68
  boris/project_ui.py,sha256=yB-ewhHt8S8DTTRIk-dNK2tPMNU24lNji9fDW_Xazu8,38805
69
69
  boris/qrc_boris.py,sha256=aH-qUirYY1CGxmTK1SFCPvuZfazIHX4DdUKF1gxZeYM,675008
70
70
  boris/qrc_boris5.py,sha256=prnOw7VGXWXRuVCYp_yIrmWhrlG1F9rx-3BQvkPenjY,161608
71
71
  boris/select_modifiers.py,sha256=42uG9F75pfPoPJ-blp-vFgmpBpVJtL42FlIxpNpq9z4,13319
72
72
  boris/select_observations.py,sha256=HM0suMes1YxVwQ-Xakw7ROaxbN0EyeSiZFZNrTQIHAA,7886
73
- boris/select_subj_behav.py,sha256=5ln2e83nVEcMW1TTeOE5K1gqd4lSvh7LeiSfhloZPrU,11537
73
+ boris/select_subj_behav.py,sha256=ulXbsRY-AIyQRSwXhVlvkNRS_eqWaCvkDKTTyOLqvoE,11742
74
74
  boris/state_events.py,sha256=R5CcT_cldnxqhQkvX_V1AMNxH0Nc6jLzMZYiWkeJwAE,7770
75
75
  boris/subjects_pad.py,sha256=lSRRGfLfD10_YpGua8RGVdKhoXlsXawGhNibPkRhuzM,3541
76
76
  boris/synthetic_time_budget.py,sha256=NwJt6TO6XZwxF2s7ETvqiDEqrz9ad30g1sQ0bVBsN9o,10364
@@ -78,7 +78,7 @@ boris/time_budget_functions.py,sha256=1-7_G84SDs7rp1EWr5zHInzRVDUkUdIfm_AX0516ce
78
78
  boris/time_budget_widget.py,sha256=_yYzLa6aJRWHRataek_ciXMEKGW75HA9y0auhAJwzbA,43137
79
79
  boris/transitions.py,sha256=_aZJfJWv3EBrtmQ7qsdTCayQo6uWU7BXqtQQgflEhr4,12250
80
80
  boris/utilities.py,sha256=zwWpH-lozAUj_8K6Gf2Hl-BSW0aRdWo37HGXTWQ86qk,52782
81
- boris/version.py,sha256=MwBl656ww2Munfb847fzP6VQ6OX5HrRbTL8ngrLBaS8,787
81
+ boris/version.py,sha256=4Axxl-LKvQ2Gomtc1ISs8xC-XrfU08mMhJ7JJObW_qk,785
82
82
  boris/video_equalizer.py,sha256=FartoGghFK-T53zklP70rPKYqTuzL8qdvfGlsOF2wwc,5854
83
83
  boris/video_equalizer_ui.py,sha256=1CG3s79eM4JAbaCx3i1ILZXLceb41_gGXlOLNfpBgnw,10142
84
84
  boris/video_operations.py,sha256=mh3iR__Sm2KnV44L_sW2pOo3AgLwlM7wiTnnqQiAVs4,9381
@@ -86,18 +86,18 @@ boris/view_df.py,sha256=AKScLASX2Uatw7rqPbsnio83eVT4GZYCFhL091eMvlY,3370
86
86
  boris/view_df_ui.py,sha256=CaMeRH_vQ00CTDDFQn73ZZaS-r8BSTWpL-dMCFqzJ_Q,2775
87
87
  boris/write_event.py,sha256=jKZWzvDvQ-wwFvTLjA-zUjtXQCWwVC49Y5g-1UctpYw,23821
88
88
  boris/analysis_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
- boris/analysis_plugins/number_of_occurences.py,sha256=U0NuU8leQixwDWlH26FsQmZl-wrvWKxNH_CWAWb8us4,1972
90
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py,sha256=fkyOuc1AiOSnrz7L6Ocd742XmDHhhQEtgi2KSiTOG4c,2252
91
- boris/analysis_plugins/time_budget.py,sha256=_eNv_9oxwa-L6pZVyATc8YNnsmUj_3Px2F0F3Y603p0,3547
89
+ boris/analysis_plugins/number_of_occurences.py,sha256=IDyDrdezqvSKT3BlD8QWpSYk8X9nnBBLI80OUnFJ3bY,509
90
+ boris/analysis_plugins/number_of_occurences_by_independent_variable.py,sha256=t39bmmmZIDCSbcDvVeiKAhKNNP2SdpHp417JczHEnP4,793
91
+ boris/analysis_plugins/time_budget.py,sha256=C1wNYwd5Jugr8h5z2aXRUBY8dF8pD4n953dPwNHY5VY,2244
92
92
  boris/portion/__init__.py,sha256=ZBUG4I7YWhRkeWdP-JEpxhxldJlUYQkeaJseTjdhtJE,602
93
93
  boris/portion/const.py,sha256=hEp26BKcEg1Js4DfZsBHmDtJJts83Tl1HWQ0CNJNwEc,1588
94
94
  boris/portion/dict.py,sha256=SyHxc7PfDw2ufNLFQycwJtzmRfL48rDp4UrM2KN7IWc,11282
95
95
  boris/portion/func.py,sha256=3TkQtFKLfsqntwd27HSGHceFhnXHmT-EbNMqktElC5Q,2174
96
96
  boris/portion/interval.py,sha256=bAdUiJjGeUAPgsBAImwNeviiwfQq5odfhFZccAWzOTA,20299
97
97
  boris/portion/io.py,sha256=ppNeRpiLNrocF1yzGeuEUIhYMf2LfsR-cji3d0nmvUs,6371
98
- boris_behav_obs-9.1.1.dist-info/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
99
- boris_behav_obs-9.1.1.dist-info/METADATA,sha256=zuFFmX5VePSe15dz0kKJdQR-TulX0i47xsTTwUkEnLw,42102
100
- boris_behav_obs-9.1.1.dist-info/WHEEL,sha256=SrDKpSbFN1G94qcmBqS9nyHcDMp9cUS9OC06hC0G3G0,109
101
- boris_behav_obs-9.1.1.dist-info/entry_points.txt,sha256=-Vl37ZFjZYK5wTSDf5LAzd2cVLON1Pfy1xkx490Wxak,47
102
- boris_behav_obs-9.1.1.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
103
- boris_behav_obs-9.1.1.dist-info/RECORD,,
98
+ boris_behav_obs-9.2.dist-info/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
99
+ boris_behav_obs-9.2.dist-info/METADATA,sha256=j6x4yuzcQMKV9DydHYBd9CQmpi7PLiuQperTRnLQrZQ,42100
100
+ boris_behav_obs-9.2.dist-info/WHEEL,sha256=SrDKpSbFN1G94qcmBqS9nyHcDMp9cUS9OC06hC0G3G0,109
101
+ boris_behav_obs-9.2.dist-info/entry_points.txt,sha256=-Vl37ZFjZYK5wTSDf5LAzd2cVLON1Pfy1xkx490Wxak,47
102
+ boris_behav_obs-9.2.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
103
+ boris_behav_obs-9.2.dist-info/RECORD,,