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.
- boris/analysis_plugins/irr_cohen_kappa.py +53 -16
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +51 -16
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +47 -10
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +47 -10
- boris/config_file.py +0 -2
- boris/export_events.py +5 -5
- boris/plugins.py +41 -3
- boris/preferences.py +23 -7
- boris/version.py +2 -2
- {boris_behav_obs-9.6.3.dist-info → boris_behav_obs-9.6.5.dist-info}/METADATA +13 -10
- {boris_behav_obs-9.6.3.dist-info → boris_behav_obs-9.6.5.dist-info}/RECORD +15 -15
- {boris_behav_obs-9.6.3.dist-info → boris_behav_obs-9.6.5.dist-info}/WHEEL +0 -0
- {boris_behav_obs-9.6.3.dist-info → boris_behav_obs-9.6.5.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.6.3.dist-info → boris_behav_obs-9.6.5.dist-info}/licenses/LICENSE.TXT +0 -0
- {boris_behav_obs-9.6.3.dist-info → boris_behav_obs-9.6.5.dist-info}/top_level.txt +0 -0
|
@@ -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.
|
|
11
|
-
__version_date__ = "2025-
|
|
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
|
-
|
|
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
|
-
#
|
|
50
|
+
# Attribute all active codes for each interval
|
|
22
51
|
def get_code(t_start, obs):
|
|
23
|
-
for seg in obs
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
#
|
|
29
|
-
|
|
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
|
-
#
|
|
32
|
-
unique_obs_list =
|
|
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:
|
|
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.
|
|
11
|
-
__version_date__ = "2025-
|
|
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
|
-
#
|
|
49
|
+
# Attribute all active codes for each interval
|
|
22
50
|
def get_code(t_start, obs):
|
|
23
|
-
for seg in obs
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
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
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)}.
|
|
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)}.
|
|
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)}.
|
|
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)}.
|
|
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)}.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
167
|
-
|
|
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("
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boris-behav-obs
|
|
3
|
-
Version: 9.6.
|
|
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
|
|
21
|
+
Requires-Dist: exifread==3.5.1
|
|
22
22
|
Requires-Dist: numpy==2.3.2
|
|
23
|
-
Requires-Dist: matplotlib
|
|
24
|
-
Requires-Dist: pandas
|
|
25
|
-
Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]
|
|
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
|
|
29
|
-
Requires-Dist: scipy
|
|
30
|
-
Requires-Dist: scikit-learn
|
|
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
|
|
52
|
+
<!-- The BO-RIS paper has more than [ 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=
|
|
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=
|
|
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=
|
|
63
|
-
boris/preferences.py,sha256
|
|
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=
|
|
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=
|
|
91
|
-
boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py,sha256=
|
|
92
|
-
boris/analysis_plugins/irr_weighted_cohen_kappa.py,sha256=
|
|
93
|
-
boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py,sha256=
|
|
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.
|
|
105
|
-
boris_behav_obs-9.6.
|
|
106
|
-
boris_behav_obs-9.6.
|
|
107
|
-
boris_behav_obs-9.6.
|
|
108
|
-
boris_behav_obs-9.6.
|
|
109
|
-
boris_behav_obs-9.6.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|