boris-behav-obs 9.6.3__py2.py3-none-any.whl → 9.6.5__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.
@@ -1,35 +1,71 @@
1
1
  """
2
2
  BORIS plugin
3
3
 
4
- Inter Rater Reliability (IRR) Unweighted Cohen Kappa
4
+ Inter Rater Reliability (IRR) Unweighted Cohen's Kappa
5
5
  """
6
6
 
7
7
  import pandas as pd
8
+ from typing import Dict, Tuple
9
+
8
10
  from sklearn.metrics import cohen_kappa_score
11
+ from PySide6.QtWidgets import QInputDialog
12
+
9
13
 
10
- __version__ = "0.0.1"
11
- __version_date__ = "2025-08-25"
12
- __plugin_name__ = "Inter Rater Reliability - Unweighted Cohen Kappa"
14
+ __version__ = "0.0.3"
15
+ __version_date__ = "2025-09-02"
16
+ __plugin_name__ = "Inter Rater Reliability - Unweighted Cohen's Kappa"
13
17
  __author__ = "Olivier Friard - University of Torino - Italy"
18
+ __description__ = """
19
+ This plugin calculates Cohen's Kappa to measure inter-rater reliability between two observers who code categorical behaviors over time intervals.
20
+ Unlike the weighted version, this approach does not take into account the duration of the intervals.
21
+ Each segment of time is treated equally, regardless of how long it lasts.
22
+ This plugin does not take into account the modifiers.
23
+
24
+ How it works:
25
+
26
+ Time segmentation
27
+ The program identifies all the time boundaries (start and end points) used by both observers.
28
+ These boundaries are merged into a common timeline, which is then divided into a set of non-overlapping elementary intervals.
29
+
30
+ Assigning codes
31
+ For each elementary interval, the program determines which behavior was coded by each observer.
14
32
 
33
+ Comparison of codes
34
+ The program builds two parallel lists of behavior codes, one for each observer.
35
+ Each elementary interval is counted as one unit of observation, no matter how long the interval actually lasts.
15
36
 
16
- def run(df: pd.DataFrame):
37
+ Cohen's Kappa calculation
38
+ Using these two lists, the program computes Cohen's Kappa using the cohen_kappa_score function of the sklearn package.
39
+ (see https://scikit-learn.org/stable/modules/generated/sklearn.metrics.cohen_kappa_score.html for details)
40
+ This coefficient measures how much the observers agree on their coding, adjusted for the amount of agreement that would be expected by chance.
41
+
42
+ """
43
+
44
+
45
+ def run(df: pd.DataFrame) -> pd.DataFrame:
17
46
  """
18
- Calculate the Inter Rater Reliability - Unweighted Cohen Kappa
47
+ Calculate the Inter Rater Reliability - Unweighted Cohen's Kappa
19
48
  """
20
49
 
21
- # attribute a code for each interval
50
+ # Attribute all active codes for each interval
22
51
  def get_code(t_start, obs):
23
- for seg in obs:
24
- if t_start >= seg[0] and t_start < seg[1]:
25
- return seg[2]
26
- return ""
52
+ active_codes = [seg[2] for seg in obs if seg[0] <= t_start < seg[1]]
53
+ if not active_codes:
54
+ return ""
55
+ # Sort to ensure deterministic representation (e.g., "A+B" instead of "B+A")
56
+ return "+".join(sorted(active_codes))
57
+
58
+ # ask user for the number of decimal places for rounding (can be negative)
59
+ round_decimals, ok = QInputDialog.getInt(
60
+ None, "Rounding", "Enter the number of decimal places for rounding (can be negative)", value=3, minValue=-5, maxValue=3, step=1
61
+ )
27
62
 
28
- # Get unique values as a numpy array
29
- unique_obs = df["Observation id"].unique()
63
+ # round times
64
+ df["Start (s)"] = df["Start (s)"].round(round_decimals)
65
+ df["Stop (s)"] = df["Stop (s)"].round(round_decimals)
30
66
 
31
- # Convert to a list
32
- unique_obs_list = unique_obs.tolist()
67
+ # Get unique values
68
+ unique_obs_list = df["Observation id"].unique().tolist()
33
69
 
34
70
  # Convert to tuples grouped by observation
35
71
  grouped = {
@@ -40,10 +76,11 @@ def run(df: pd.DataFrame):
40
76
  for obs, group in df.groupby("Observation id")
41
77
  }
42
78
 
43
- ck_results: dict = {}
79
+ ck_results: Dict[Tuple[str, str], str] = {}
44
80
  for idx1, obs_id1 in enumerate(unique_obs_list):
45
81
  obs1 = grouped[obs_id1]
46
82
 
83
+ # Perfect agreement with itself
47
84
  ck_results[(obs_id1, obs_id1)] = "1.000"
48
85
 
49
86
  for obs_id2 in unique_obs_list[idx1 + 1 :]:
@@ -1,35 +1,70 @@
1
1
  """
2
2
  BORIS plugin
3
3
 
4
- Inter Rater Reliability (IRR) Unweighted Cohen Kappa with modifiers
4
+ Inter Rater Reliability (IRR) Unweighted Cohen's Kappa with modifiers
5
5
  """
6
6
 
7
7
  import pandas as pd
8
+
8
9
  from sklearn.metrics import cohen_kappa_score
10
+ from PySide6.QtWidgets import QInputDialog
9
11
 
10
- __version__ = "0.0.1"
11
- __version_date__ = "2025-08-25"
12
- __plugin_name__ = "Inter Rater Reliability - Unweighted Cohen Kappa with modifiers"
12
+ __version__ = "0.0.3"
13
+ __version_date__ = "2025-09-02"
14
+ __plugin_name__ = "Inter Rater Reliability - Unweighted Cohen's Kappa with modifiers"
13
15
  __author__ = "Olivier Friard - University of Torino - Italy"
16
+ __description__ = """
17
+ This plugin calculates Cohen's Kappa to measure inter-rater reliability between two observers who code categorical behaviors over time intervals.
18
+ Unlike the weighted version, this approach does not take into account the duration of the intervals.
19
+ Each segment of time is treated equally, regardless of how long it lasts.
20
+ This plugin takes into account the modifiers.
21
+
22
+
23
+ How it works:
24
+
25
+ Time segmentation
26
+ The program identifies all the time boundaries (start and end points) used by both observers.
27
+ These boundaries are merged into a common timeline, which is then divided into a set of non-overlapping elementary intervals.
28
+
29
+ Assigning codes
30
+ For each elementary interval, the program determines which behavior was coded by each observer.
31
+
32
+ Comparison of codes
33
+ The program builds two parallel lists of behavior codes, one for each observer.
34
+ Each elementary interval is counted as one unit of observation, no matter how long the interval actually lasts.
35
+
36
+ Cohen's Kappa calculation
37
+ Using these two lists, the program computes Cohen's Kappa using the cohen_kappa_score function of the sklearn package.
38
+ (see https://scikit-learn.org/stable/modules/generated/sklearn.metrics.cohen_kappa_score.html for details)
39
+ This coefficient measures how much the observers agree on their coding, adjusted for the amount of agreement that would be expected by chance.
40
+
41
+ """
14
42
 
15
43
 
16
44
  def run(df: pd.DataFrame):
17
45
  """
18
- Calculate the Inter Rater Reliability - Unweighted Cohen Kappa with modifiers
46
+ Calculate the Inter Rater Reliability - Unweighted Cohen's Kappa with modifiers
19
47
  """
20
48
 
21
- # attribute a code for each interval
49
+ # Attribute all active codes for each interval
22
50
  def get_code(t_start, obs):
23
- for seg in obs:
24
- if t_start >= seg[0] and t_start < seg[1]:
25
- return seg[2]
26
- return ""
27
-
28
- # Get unique values as a numpy array
29
- unique_obs = df["Observation id"].unique()
30
-
31
- # Convert to a list
32
- unique_obs_list = unique_obs.tolist()
51
+ active_codes = [seg[2] for seg in obs if seg[0] <= t_start < seg[1]]
52
+ if not active_codes:
53
+ return ""
54
+ # Sort to ensure deterministic representation (e.g., "A+B" instead of "B+A")
55
+ return "+".join(sorted(active_codes))
56
+
57
+ # ask user for the number of decimal places for rounding (can be negative)
58
+ round_decimals, ok = QInputDialog.getInt(
59
+ None, "Rounding", "Enter the number of decimal places for rounding (can be negative)", value=3, minValue=-5, maxValue=3, step=1
60
+ )
61
+
62
+ # round times
63
+ df["Start (s)"] = df["Start (s)"].round(round_decimals)
64
+ df["Stop (s)"] = df["Stop (s)"].round(round_decimals)
65
+
66
+ # Get unique values
67
+ unique_obs_list = df["Observation id"].unique().tolist()
33
68
 
34
69
  # Convert to tuples grouped by observation
35
70
  grouped: dict = {}
@@ -1,21 +1,49 @@
1
1
  """
2
2
  BORIS plugin
3
3
 
4
- Inter Rater Reliability (IRR) Weighted Cohen Kappa
4
+ Inter Rater Reliability (IRR) Weighted Cohen's Kappa
5
5
  """
6
6
 
7
7
  import pandas as pd
8
8
  from typing import List, Tuple, Dict, Optional
9
9
 
10
- __version__ = "0.0.1"
11
- __version_date__ = "2025-08-25"
12
- __plugin_name__ = "Inter Rater Reliability - Weighted Cohen Kappa"
10
+ from PySide6.QtWidgets import QInputDialog
11
+
12
+ __version__ = "0.0.3"
13
+ __version_date__ = "2025-09-02"
14
+ __plugin_name__ = "Inter Rater Reliability - Weighted Cohen's Kappa"
13
15
  __author__ = "Olivier Friard - University of Torino - Italy"
16
+ __description__ = """
17
+ This plugin calculates Cohen's Kappa to measure inter-rater reliability between two observers who code categorical behaviors over time intervals.
18
+ Unlike the unweighted version, this approach takes into account the duration of each coded interval, giving more weight to longer intervals in the agreement calculation.
19
+ This plugin does not take into account the modifiers.
20
+
21
+ How it works:
22
+
23
+ Time segmentation
24
+ The program collects all the time boundaries from both observers and merges them into a unified set of time points.
25
+ These define a set of non-overlapping elementary intervals covering the entire observed period.
26
+
27
+ Assigning codes
28
+ For each elementary interval, the program identifies the behavior category assigned by each observer.
29
+
30
+ Weighted contingency table
31
+ Instead of treating each interval equally, the program assigns a weight equal to the duration of the interval.
32
+ These durations are accumulated in a contingency table that records how much time was spent in each combination of categories across the two observers.
33
+
34
+ Agreement calculation
35
+
36
+ Observed agreement (po): The proportion of total time where both observers assigned the same category.
37
+
38
+ Expected agreement (pe): The proportion of agreement expected by chance, based on the time-weighted marginal distributions of each observer's coding.
39
+
40
+ Cohen's Kappa (κ): Computed from the weighted observed and expected agreements.
41
+ """
14
42
 
15
43
 
16
44
  def run(df: pd.DataFrame):
17
45
  """
18
- Calculate the Inter Rater Reliability - Weighted Cohen Kappa
46
+ Calculate the Inter Rater Reliability - Weighted Cohen's Kappa
19
47
  """
20
48
 
21
49
  def cohen_kappa_weighted_by_time(
@@ -41,12 +69,12 @@ def run(df: pd.DataFrame):
41
69
  # 2. Build elementary intervals (non-overlapping time bins)
42
70
  elementary_intervals = [(time_points[i], time_points[i + 1]) for i in range(len(time_points) - 1)]
43
71
 
44
- # 3. Helper: get the active code for an observer at a given time
72
+ # 3. # Attribute all active codes for each interval
45
73
  def get_code(t: float, obs: List[Tuple[float, float, str]]) -> Optional[str]:
46
- for seg in obs:
47
- if seg[0] <= t < seg[1]:
48
- return seg[2]
49
- return None # in case no segment covers this time
74
+ active_codes = [seg[2] for seg in obs if seg[0] <= t < seg[1]]
75
+ if not active_codes:
76
+ return None
77
+ return "+".join(sorted(active_codes))
50
78
 
51
79
  # 4. Build weighted contingency table (durations instead of counts)
52
80
  contingency: Dict[Tuple[Optional[str], Optional[str]], float] = {}
@@ -78,6 +106,15 @@ def run(df: pd.DataFrame):
78
106
 
79
107
  return kappa, po, pe, contingency
80
108
 
109
+ # ask user for the number of decimal places for rounding (can be negative)
110
+ round_decimals, ok = QInputDialog.getInt(
111
+ None, "Rounding", "Enter the number of decimal places for rounding (can be negative)", value=3, minValue=-5, maxValue=3, step=1
112
+ )
113
+
114
+ # round times
115
+ df["Start (s)"] = df["Start (s)"].round(round_decimals)
116
+ df["Stop (s)"] = df["Stop (s)"].round(round_decimals)
117
+
81
118
  # Get unique values as a numpy array
82
119
  unique_obs = df["Observation id"].unique()
83
120
 
@@ -1,21 +1,49 @@
1
1
  """
2
2
  BORIS plugin
3
3
 
4
- Inter Rater Reliability (IRR) Weighted Cohen Kappa with modifiers
4
+ Inter Rater Reliability (IRR) Weighted Cohen's Kappa with modifiers
5
5
  """
6
6
 
7
7
  import pandas as pd
8
8
  from typing import List, Tuple, Dict, Optional
9
9
 
10
- __version__ = "0.0.1"
11
- __version_date__ = "2025-08-25"
12
- __plugin_name__ = "Inter Rater Reliability - Weighted Cohen Kappa with modifiers"
10
+ from PySide6.QtWidgets import QInputDialog
11
+
12
+ __version__ = "0.0.3"
13
+ __version_date__ = "2025-09-02"
14
+ __plugin_name__ = "Inter Rater Reliability - Weighted Cohen's Kappa with modifiers"
13
15
  __author__ = "Olivier Friard - University of Torino - Italy"
16
+ __description__ = """
17
+ This plugin calculates Cohen's Kappa to measure inter-rater reliability between two observers who code categorical behaviors over time intervals.
18
+ Unlike the unweighted version, this approach takes into account the duration of each coded interval, giving more weight to longer intervals in the agreement calculation.
19
+ This plugin takes into account the modifiers.
20
+
21
+ How it works:
22
+
23
+ Time segmentation
24
+ The program collects all the time boundaries from both observers and merges them into a unified set of time points.
25
+ These define a set of non-overlapping elementary intervals covering the entire observed period.
26
+
27
+ Assigning codes
28
+ For each elementary interval, the program identifies the behavior category assigned by each observer.
29
+
30
+ Weighted contingency table
31
+ Instead of treating each interval equally, the program assigns a weight equal to the duration of the interval.
32
+ These durations are accumulated in a contingency table that records how much time was spent in each combination of categories across the two observers.
33
+
34
+ Agreement calculation
35
+
36
+ Observed agreement (po): The proportion of total time where both observers assigned the same category.
37
+
38
+ Expected agreement (pe): The proportion of agreement expected by chance, based on the time-weighted marginal distributions of each observer's coding.
39
+
40
+ Cohen's Kappa (κ): Computed from the weighted observed and expected agreements.
41
+ """
14
42
 
15
43
 
16
44
  def run(df: pd.DataFrame):
17
45
  """
18
- Calculate the Inter Rater Reliability - Weighted Cohen Kappa with modifiers
46
+ Calculate the Inter Rater Reliability - Weighted Cohen's Kappa with modifiers
19
47
  """
20
48
 
21
49
  def cohen_kappa_weighted_by_time(
@@ -41,12 +69,12 @@ def run(df: pd.DataFrame):
41
69
  # 2. Build elementary intervals (non-overlapping time bins)
42
70
  elementary_intervals = [(time_points[i], time_points[i + 1]) for i in range(len(time_points) - 1)]
43
71
 
44
- # 3. Helper: get the active code for an observer at a given time
72
+ # 3. Attribute all active codes for each interval
45
73
  def get_code(t: float, obs: List[Tuple[float, float, str]]) -> Optional[str]:
46
- for seg in obs:
47
- if seg[0] <= t < seg[1]:
48
- return seg[2]
49
- return None # in case no segment covers this time
74
+ active_codes = [seg[2] for seg in obs if seg[0] <= t < seg[1]]
75
+ if not active_codes:
76
+ return None
77
+ return "+".join(sorted(active_codes))
50
78
 
51
79
  # 4. Build weighted contingency table (durations instead of counts)
52
80
  contingency: Dict[Tuple[Optional[str], Optional[str]], float] = {}
@@ -78,6 +106,15 @@ def run(df: pd.DataFrame):
78
106
 
79
107
  return kappa, po, pe, contingency
80
108
 
109
+ # ask user for the number of decimal places for rounding (can be negative)
110
+ round_decimals, ok = QInputDialog.getInt(
111
+ None, "Rounding", "Enter the number of decimal places for rounding (can be negative)", value=3, minValue=-5, maxValue=3, step=1
112
+ )
113
+
114
+ # round times
115
+ df["Start (s)"] = df["Start (s)"].round(round_decimals)
116
+ df["Stop (s)"] = df["Stop (s)"].round(round_decimals)
117
+
81
118
  # Get unique values as a numpy array
82
119
  unique_obs = df["Observation id"].unique()
83
120
 
boris/config_file.py CHANGED
@@ -169,8 +169,6 @@ def read(self):
169
169
  # check for new version
170
170
  self.checkForNewVersion = False
171
171
 
172
- # print(f"{self.no_first_launch_dialog=}")
173
-
174
172
  if not self.no_first_launch_dialog:
175
173
  try:
176
174
  if settings.value("check_for_new_version") is None:
boris/export_events.py CHANGED
@@ -979,12 +979,12 @@ def export_events_as_textgrid(self) -> None:
979
979
  continue
980
980
 
981
981
  # check if file already exists
982
- if mem_command != cfg.OVERWRITE_ALL and pl.Path(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid").is_file():
982
+ if mem_command != cfg.OVERWRITE_ALL and pl.Path(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid").is_file():
983
983
  if mem_command == cfg.SKIP_ALL:
984
984
  continue
985
985
  mem_command = dialog.MessageDialog(
986
986
  cfg.programName,
987
- f"The file <b>{pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid</b> already exists.",
987
+ f"The file <b>{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid</b> already exists.",
988
988
  [cfg.OVERWRITE, cfg.OVERWRITE_ALL, cfg.SKIP, cfg.SKIP_ALL, cfg.CANCEL],
989
989
  )
990
990
  if mem_command == cfg.CANCEL:
@@ -993,13 +993,13 @@ def export_events_as_textgrid(self) -> None:
993
993
  continue
994
994
 
995
995
  try:
996
- with open(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid", "w") as f:
996
+ with open(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid", "w") as f:
997
997
  f.write(out)
998
998
  file_count += 1
999
- self.results.ptText.appendHtml(f"File {pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid was created.")
999
+ self.results.ptText.appendHtml(f"File {pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid was created.")
1000
1000
  QApplication.processEvents()
1001
1001
  except Exception:
1002
- self.results.ptText.appendHtml(f"The file {pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid can not be created.")
1002
+ self.results.ptText.appendHtml(f"The file {pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid can not be created.")
1003
1003
  QApplication.processEvents()
1004
1004
 
1005
1005
  self.results.ptText.appendHtml(f"Done. {file_count} file(s) were created in {export_dir}.")
boris/plugins.py CHANGED
@@ -105,9 +105,11 @@ def get_r_plugin_description(plugin_path: str) -> str | None:
105
105
 
106
106
  def load_plugins(self):
107
107
  """
108
- load selected plugins in analysis menu
108
+ load selected plugins in config_param
109
109
  """
110
110
 
111
+ logging.debug("Loading plugins")
112
+
111
113
  def msg():
112
114
  QMessageBox.warning(
113
115
  self,
@@ -124,7 +126,25 @@ def load_plugins(self):
124
126
  for file_ in sorted((Path(__file__).parent / "analysis_plugins").glob("*.py")):
125
127
  if file_.name.startswith("_"):
126
128
  continue
127
- plugin_name = get_plugin_name(file_)
129
+
130
+ logging.debug(f"Loading plugin: {Path(file_).stem}")
131
+
132
+ # test module
133
+ module_name = Path(file_).stem
134
+ spec = importlib.util.spec_from_file_location(module_name, file_)
135
+ plugin_module = importlib.util.module_from_spec(spec)
136
+ spec.loader.exec_module(plugin_module)
137
+ attributes_list = dir(plugin_module)
138
+
139
+ if "__plugin_name__" in attributes_list:
140
+ plugin_name = plugin_module.__plugin_name__
141
+ else:
142
+ continue
143
+
144
+ if "run" not in attributes_list:
145
+ continue
146
+
147
+ # plugin_name = get_plugin_name(file_)
128
148
  if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
129
149
  # check if plugin with same name already loaded
130
150
  if plugin_name in self.config_param[cfg.ANALYSIS_PLUGINS]:
@@ -138,7 +158,25 @@ def load_plugins(self):
138
158
  for file_ in sorted(Path(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, "")).glob("*.py")):
139
159
  if file_.name.startswith("_"):
140
160
  continue
141
- plugin_name = get_plugin_name(file_)
161
+
162
+ logging.debug(f"Loading personal plugin: {Path(file_).stem}")
163
+
164
+ # test module
165
+ module_name = Path(file_).stem
166
+ spec = importlib.util.spec_from_file_location(module_name, file_)
167
+ plugin_module = importlib.util.module_from_spec(spec)
168
+ spec.loader.exec_module(plugin_module)
169
+ attributes_list = dir(plugin_module)
170
+
171
+ if "__plugin_name__" in attributes_list:
172
+ plugin_name = plugin_module.__plugin_name__
173
+ else:
174
+ continue
175
+
176
+ if "run" not in attributes_list:
177
+ continue
178
+
179
+ # plugin_name = get_plugin_name(file_)
142
180
  if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
143
181
  # check if plugin with same name already loaded
144
182
  if plugin_name in self.config_param[cfg.ANALYSIS_PLUGINS]:
boris/preferences.py CHANGED
@@ -60,7 +60,7 @@ class Preferences(QDialog, Ui_prefDialog):
60
60
  # Create a monospace QFont
61
61
  monospace_font = QFont("Courier New") # or "Monospace", "Consolas", "Liberation Mono", etc.
62
62
  monospace_font.setStyleHint(QFont.Monospace)
63
- monospace_font.setPointSize(13)
63
+ monospace_font.setPointSize(12)
64
64
  self.pte_plugin_code.setFont(monospace_font)
65
65
 
66
66
  def browse_plugins_dir(self):
@@ -83,7 +83,6 @@ class Preferences(QDialog, Ui_prefDialog):
83
83
  if plugin_name in [self.lv_all_plugins.item(i).text() for i in range(self.lv_all_plugins.count())]:
84
84
  continue
85
85
  item = QListWidgetItem(plugin_name)
86
- # item = QListWidgetItem(file_.stem)
87
86
  item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
88
87
  item.setCheckState(Qt.Checked)
89
88
  item.setData(100, str(file_))
@@ -152,6 +151,7 @@ def preferences(self):
152
151
 
153
152
  plugin_path = item.data(100)
154
153
 
154
+ # Python plugins
155
155
  if Path(plugin_path).suffix == ".py":
156
156
  import importlib
157
157
 
@@ -159,21 +159,35 @@ def preferences(self):
159
159
  spec = importlib.util.spec_from_file_location(module_name, plugin_path)
160
160
  plugin_module = importlib.util.module_from_spec(spec)
161
161
  spec.loader.exec_module(plugin_module)
162
+ attributes_list = dir(plugin_module)
162
163
 
163
164
  out: list = []
164
- out.append(plugin_module.__plugin_name__ + "\n")
165
- out.append(plugin_module.__author__)
166
- out.append(f"{plugin_module.__version__} ({plugin_module.__version_date__})\n")
167
- out.append(plugin_module.run.__doc__.strip())
165
+ out.append((plugin_module.__plugin_name__ + "\n") if "__plugin_name__" in attributes_list else "No plugin name provided")
166
+ out.append(plugin_module.__author__ if "__author__" in attributes_list else "No author provided")
167
+ version_str: str = ""
168
+ if "__version__" in attributes_list:
169
+ version_str += str(plugin_module.__version__)
170
+ if "__version_date__" in attributes_list:
171
+ version_str += " " if version_str else ""
172
+ version_str += f"({plugin_module.__version_date__})"
173
+
174
+ out.append(f"Version: {version_str}\n" if version_str else "No version provided")
175
+
176
+ # out.append(plugin_module.run.__doc__.strip())
177
+ # description
178
+ if "__description__" in attributes_list:
179
+ out.append("Description:\n")
180
+ out.append(plugin_module.__description__ if "__description__" in attributes_list else "No description provided")
168
181
 
169
182
  preferencesWindow.pte_plugin_description.setPlainText("\n".join(out))
170
183
 
184
+ # R plugins
171
185
  if Path(plugin_path).suffix == ".R":
172
186
  plugin_description = plugins.get_r_plugin_description(plugin_path)
173
187
  if plugin_description is not None:
174
188
  preferencesWindow.pte_plugin_description.setPlainText("\n".join(plugin_description.split("\\n")))
175
189
  else:
176
- preferencesWindow.pte_plugin_description.setPlainText("Plugin description not found")
190
+ preferencesWindow.pte_plugin_description.setPlainText("No description provided")
177
191
 
178
192
  # display plugin code
179
193
  try:
@@ -254,6 +268,7 @@ def preferences(self):
254
268
 
255
269
  preferencesWindow.lw_personal_plugins.clear()
256
270
  if self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""):
271
+ # Python plugins
257
272
  for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.py"):
258
273
  if file_.name.startswith("_"):
259
274
  continue
@@ -272,6 +287,7 @@ def preferences(self):
272
287
  item.setData(100, str(file_))
273
288
  preferencesWindow.lw_personal_plugins.addItem(item)
274
289
 
290
+ # R plugins
275
291
  for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.R"):
276
292
  plugin_name = plugins.get_r_plugin_name(file_)
277
293
  if plugin_name is None:
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.6.3"
24
- __version_date__ = "2025-08-26"
23
+ __version__ = "9.6.5"
24
+ __version_date__ = "2025-09-02"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.6.3
3
+ Version: 9.6.5
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License-Expression: GPL-3.0-only
@@ -18,16 +18,16 @@ Classifier: Topic :: Scientific/Engineering
18
18
  Requires-Python: >=3.12
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.TXT
21
- Requires-Dist: exifread>=3.0.0
21
+ Requires-Dist: exifread==3.5.1
22
22
  Requires-Dist: numpy==2.3.2
23
- Requires-Dist: matplotlib>=3.3.3
24
- Requires-Dist: pandas>=2.2.2
25
- Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]>=3
26
- Requires-Dist: pyreadr
23
+ Requires-Dist: matplotlib==3.10.5
24
+ Requires-Dist: pandas==2.3.2
25
+ Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]==3.8.0
26
+ Requires-Dist: pyreadr==0.5.3
27
27
  Requires-Dist: pyside6==6.9
28
- Requires-Dist: hachoir>=3.3.0
29
- Requires-Dist: scipy>=1.15.3
30
- Requires-Dist: scikit-learn>=1.7.1
28
+ Requires-Dist: hachoir==3.3.0
29
+ Requires-Dist: scipy==1.16.1
30
+ Requires-Dist: scikit-learn==1.7.1
31
31
  Provides-Extra: dev
32
32
  Requires-Dist: ruff; extra == "dev"
33
33
  Requires-Dist: pytest; extra == "dev"
@@ -49,7 +49,10 @@ You can not longer run BORIS natively on MacOS (since v.8). Some alternatives to
49
49
 
50
50
  It provides also some analysis tools like time budget and some plotting functions.
51
51
 
52
- The BORIS paper has more than [![BORIS citations counter](https://penelope.unito.it/friard/boris_scopus_citations.png) citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications.
52
+ <!-- The BO-RIS paper has more than [![BORIS citations counter](https://penelope.unito.it/friard/boris_scopus_citations.png) citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications. -->
53
+
54
+
55
+ The BORIS paper has more than 2336 citations in peer-reviewed scientific publications.
53
56
 
54
57
 
55
58
 
@@ -11,7 +11,7 @@ boris/boris_cli.py,sha256=Bc51DEMcD79ZZfM9pCzpaWU6iT6b8gNwR3n8fr42_4E,13193
11
11
  boris/cmd_arguments.py,sha256=oWb-FvhKLbKJhATlTHy9muWu8XnnUfOZ-3Fmz2M8Yzc,1848
12
12
  boris/coding_pad.py,sha256=BaDWYIzoRgl0LHymPDmcBMFfwG7Z0XROqqMwkkADtz0,10940
13
13
  boris/config.py,sha256=IbW8PkAFcZIL-8NoSscXSeI82dneLzpywaGXWDcnrWw,17845
14
- boris/config_file.py,sha256=bPDDRjtylVJ2ll-FNRjME5iIsIagonZNFns-k9kGW2Q,13545
14
+ boris/config_file.py,sha256=5_AB_VE5j3iHkanL5xELN42HJJMLOh40qSgBFs7fCXo,13493
15
15
  boris/connections.py,sha256=kqc6jaYNzoJe8crPtfwE-fXTW4nTfB8-PojRzbbLEus,19629
16
16
  boris/converters.py,sha256=n6gDM9x2hS-ZOoHLruiifuXxnC7ERsUukiFokhHZPoQ,11678
17
17
  boris/converters_ui.py,sha256=uu7LOBV_fKv2DBdOqsqPwjGsjgONr5ODBoscAA-EP48,9900
@@ -29,7 +29,7 @@ boris/event_operations.py,sha256=bqUZjgJaJ1Z8oTiidG9wfCp2LLUH1Zf4kBDeg_yjC-o,385
29
29
  boris/events_cursor.py,sha256=VPY_ygD0fxE5lp25mcd2l00XQXurCR6hprffF4tKRbU,2078
30
30
  boris/events_snapshots.py,sha256=PjWzQvUGQtIcEc_7FDsRphf7fAhhTccQgYc2eQSA65g,27621
31
31
  boris/exclusion_matrix.py,sha256=K_o8pEMYRQ3curgRQYkn5hPRksLDitICuwjB7mpVRPA,5269
32
- boris/export_events.py,sha256=kbGeBFiJzND2K7Y-iUvmhzMDTeyDSY9hwSO1XGJ0ij0,39745
32
+ boris/export_events.py,sha256=BxDYxfhkS99d9L3-egrri3lHFp3X_4Tl3F8ZrnE1yVU,39745
33
33
  boris/export_observation.py,sha256=B8ASj6H70xfcTSUHrbcJa6YOYjih2WD4DSiUtXj5eAk,50764
34
34
  boris/external_processes.py,sha256=PogE2eEiQLVZ2useMamQMOAeDmMUX_TlIpqPKLMm6Ak,13607
35
35
  boris/geometric_measurement.py,sha256=4pI-AYpBSFlJBqS-f8dnkgLtj_Z2E5kwwAdh6WwZ4kk,35049
@@ -59,8 +59,8 @@ boris/plot_events.py,sha256=tKiUWH0TNSkK7xz5Vf0tAD3KiuAalv6UZEVtOnoFpWY,24059
59
59
  boris/plot_events_rt.py,sha256=xJmjwqhQxCN4FDBYRQ0O2eHm356Rbexzr3m1qTefMDU,8326
60
60
  boris/plot_spectrogram_rt.py,sha256=wDhnkqwjd2UfCxrfOejOUxoNOqfMNo6vo1JSvYgM-2A,10925
61
61
  boris/plot_waveform_rt.py,sha256=RNXhcBzRKnoGoVjRAHsVvOaj0BJbbI2cpCMjMUiGqX0,7534
62
- boris/plugins.py,sha256=lYR_sLqRMdyGEoSPwaLyVuF50B92k6vR8TlDAE5B4l0,14252
63
- boris/preferences.py,sha256=gWkqKvKuAAzjNbL3_NdBeaHfNC5xKQVxVZW4J1OwYRg,20763
62
+ boris/plugins.py,sha256=wZ-Azq_4xD_vZf8t-cPm4jT-6kWw_t0lroGYMMZY8bE,15468
63
+ boris/preferences.py,sha256=-WwBlkP3uhSyO3tUhcrctHMzlv46ngW9C4KAq7UJAoI,21563
64
64
  boris/preferences_ui.py,sha256=wbo51aBNdcQTJni1DmUM5ZQPOwAtKSkEQam7rRzRS5g,34166
65
65
  boris/project.py,sha256=nyXfCDY_rLP3jC1QGv-280jUKgbABqESjOm7I19rJ1U,86432
66
66
  boris/project_functions.py,sha256=o0IOvhGs1cqEjpdeNUeY-qvFfWAQl_7tsUEKxogKRuU,80869
@@ -78,7 +78,7 @@ boris/time_budget_functions.py,sha256=SbGyTF2xySEqBdRJZeWFTirruvK3r6pwM6e4Gz17W1
78
78
  boris/time_budget_widget.py,sha256=z-tyITBtIz-KH1H2OdMB5a8x9QQLK7Wu96-zkC6NVDA,43213
79
79
  boris/transitions.py,sha256=okyDCO-Vn4p_Fixd8cGiSIaUhUxG5ePIOqGSuP52g_c,12246
80
80
  boris/utilities.py,sha256=dD5HpojqlAGLVkr3YnOsaqfbCMHFYroe040ZchB5WnM,56662
81
- boris/version.py,sha256=HGbYNN1RSRh_r7nFEFuDLgzfshiVd5wpkEXEYQi_HEw,787
81
+ boris/version.py,sha256=TvVlNsadnocZHS1Z6ou_pjeTRRwHbRfwriUKmuuyeYA,787
82
82
  boris/video_equalizer.py,sha256=cm2JXe1eAPkqDzxYB2OMKyuveMBdq4a9-gD1bBorbn4,5823
83
83
  boris/video_equalizer_ui.py,sha256=1CG3s79eM4JAbaCx3i1ILZXLceb41_gGXlOLNfpBgnw,10142
84
84
  boris/video_operations.py,sha256=rXKWndaALaF-yLEPIY_-Z99XRAncZRzRd1sLzwSpbjs,10768
@@ -87,10 +87,10 @@ boris/view_df_ui.py,sha256=CaMeRH_vQ00CTDDFQn73ZZaS-r8BSTWpL-dMCFqzJ_Q,2775
87
87
  boris/write_event.py,sha256=xC-dUjgPS4-tvrQfvyL7O_xi-tyQI7leSiSV8_x8hgg,23817
88
88
  boris/analysis_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  boris/analysis_plugins/_latency.py,sha256=9kCdFDtb5Zdao1xFpioi_exm_IxyGm6RlY3Opn6GUFo,2030
90
- boris/analysis_plugins/irr_cohen_kappa.py,sha256=UtIZ_y5Wm9UbZpGI67a-AszYnH_5DV1LNcff76ChQYg,2253
91
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py,sha256=olYp-L5DZ71vs9B-k9PyErLY5-O2d-R69TUYx6mAzj4,2563
92
- boris/analysis_plugins/irr_weighted_cohen_kappa.py,sha256=ZEV9PHq0IfPmGBP3gxgnDlnUyM_NXmEIhqqltUHMGwU,4561
93
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py,sha256=of4EVdUPCpre-HH5l9d1yD-19vj0Judn60OM0Km40EQ,4880
90
+ boris/analysis_plugins/irr_cohen_kappa.py,sha256=OqmivIE6i1hTcFVMp0EtY0Sr7C1Jm9t0D4IKbDfTJ7U,4268
91
+ boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py,sha256=DtzFLRToR9GdkmWYDcCmpSvxHGpguVp-_n8F-t7ND7c,4461
92
+ boris/analysis_plugins/irr_weighted_cohen_kappa.py,sha256=xpDjcDkwPtTM3SI9UC2RIhma1nqFU_qcGSMq7QpMMHc,6435
93
+ boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py,sha256=DXrvac4p3_jzakHi_hTW8ZG9rniGo4YCyUH_jJ7fz8c,6744
94
94
  boris/analysis_plugins/list_of_dataframe_columns.py,sha256=VEiVhxADtyaIKN4JrfFV02TuTAfWhQ60bf1mHVQp27I,437
95
95
  boris/analysis_plugins/number_of_occurences.py,sha256=IDyDrdezqvSKT3BlD8QWpSYk8X9nnBBLI80OUnFJ3bY,509
96
96
  boris/analysis_plugins/number_of_occurences_by_independent_variable.py,sha256=_7HTKXsyxNfyO69tP8zkQEHzT0C7qHdL1sqBjnUfRQY,1459
@@ -101,9 +101,9 @@ boris/portion/dict.py,sha256=uNM-LEY52CZ2VNMMW_C9QukoyTvPlQf8vcbGa1lQBHI,11281
101
101
  boris/portion/func.py,sha256=mSQr20YS1ug7R1fRqBg8LifjtXDRvJ6Kjc3WOeL9P34,2172
102
102
  boris/portion/interval.py,sha256=sOlj3MAGGaB-JxCkigS-n3qw0fY7TANAsXv1pavr8J4,19931
103
103
  boris/portion/io.py,sha256=kpq44pw3xnIyAlPwaR5qRHKRdZ72f8HS9YVIWs5k2pk,6367
104
- boris_behav_obs-9.6.3.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
105
- boris_behav_obs-9.6.3.dist-info/METADATA,sha256=afO9LamHovrzbJeX6-3ppvUVgwV-BQFKgiBj5ffWrrQ,4978
106
- boris_behav_obs-9.6.3.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
107
- boris_behav_obs-9.6.3.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
108
- boris_behav_obs-9.6.3.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
109
- boris_behav_obs-9.6.3.dist-info/RECORD,,
104
+ boris_behav_obs-9.6.5.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
105
+ boris_behav_obs-9.6.5.dist-info/METADATA,sha256=SMRqLS1ScpTMc2n42ZfIiIFsNX6zq5lIOGcyReqTyrs,5089
106
+ boris_behav_obs-9.6.5.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
107
+ boris_behav_obs-9.6.5.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
108
+ boris_behav_obs-9.6.5.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
109
+ boris_behav_obs-9.6.5.dist-info/RECORD,,