boris-behav-obs 9.7.12__py3-none-any.whl → 9.8.2__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/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +4 -3
- boris/add_modifier.py +1 -1
- boris/advanced_event_filtering.py +1 -1
- boris/analysis_plugins/export_to_feral.py +336 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +2 -2
- boris/behav_coding_map_creator.py +1 -1
- boris/behavior_binary_table.py +1 -1
- boris/behaviors_coding_map.py +1 -1
- boris/boris_cli.py +1 -1
- boris/cmd_arguments.py +1 -1
- boris/coding_pad.py +1 -1
- boris/config.py +15 -3
- boris/config_file.py +18 -19
- boris/connections.py +12 -13
- boris/converters.py +1 -1
- boris/converters_ui.py +2 -3
- boris/cooccurence.py +1 -1
- boris/core.py +168 -166
- boris/core_qrc.py +1830 -1967
- boris/core_ui.py +1 -1
- boris/db_functions.py +5 -14
- boris/dialog.py +24 -24
- boris/edit_event.py +1 -1
- boris/event_operations.py +1 -1
- boris/events_cursor.py +1 -1
- boris/events_snapshots.py +133 -78
- boris/exclusion_matrix.py +1 -1
- boris/export_events.py +49 -43
- boris/export_observation.py +1 -1
- boris/external_processes.py +1 -1
- boris/geometric_measurement.py +1 -1
- boris/gui_utilities.py +1 -1
- boris/image_overlay.py +1 -1
- boris/import_observations.py +1 -1
- boris/ipc_mpv.py +1 -1
- boris/irr.py +1 -1
- boris/latency.py +1 -1
- boris/measurement_widget.py +1 -1
- boris/media_file.py +1 -1
- boris/menu_options.py +14 -12
- boris/modifier_coding_map_creator.py +1 -1
- boris/modifiers_coding_map.py +1 -1
- boris/observation.py +13 -14
- boris/observation_operations.py +1 -1
- boris/observations_list.py +1 -1
- boris/otx_parser.py +1 -1
- boris/param_panel.py +1 -1
- boris/player_dock_widget.py +1 -1
- boris/plot_data_module.py +1 -1
- boris/plot_events.py +1 -1
- boris/plot_events_rt.py +1 -1
- boris/plot_spectrogram_rt.py +42 -73
- boris/plot_waveform_rt.py +1 -1
- boris/plugins.py +1 -1
- boris/preferences.py +35 -4
- boris/preferences_ui.py +48 -18
- boris/project.py +1 -1
- boris/project_functions.py +19 -22
- boris/project_import_export.py +1 -1
- boris/select_modifiers.py +1 -1
- boris/select_observations.py +22 -23
- boris/select_subj_behav.py +4 -4
- boris/state_events.py +1 -1
- boris/subjects_pad.py +1 -1
- boris/synthetic_time_budget.py +1 -1
- boris/time_budget_functions.py +1 -1
- boris/time_budget_widget.py +1 -1
- boris/transitions.py +1 -1
- boris/utilities.py +1 -1
- boris/version.py +3 -3
- boris/video_equalizer.py +1 -1
- boris/video_operations.py +1 -1
- boris/view_df.py +28 -4
- boris/write_event.py +1 -1
- {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/METADATA +2 -2
- boris_behav_obs-9.8.2.dist-info/RECORD +110 -0
- {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/WHEEL +1 -1
- boris/analysis_plugins/_export_to_feral.py +0 -225
- boris_behav_obs-9.7.12.dist-info/RECORD +0 -110
- {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/licenses/LICENSE.TXT +0 -0
- {boris_behav_obs-9.7.12.dist-info → boris_behav_obs-9.8.2.dist-info}/top_level.txt +0 -0
boris/select_observations.py
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
5
|
|
|
6
|
+
This file is part of BORIS.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
BORIS is free software; you can redistribute it and/or modify
|
|
8
9
|
it under the terms of the GNU General Public License as published by
|
|
9
|
-
the Free Software Foundation; either version
|
|
10
|
-
|
|
10
|
+
the Free Software Foundation; either version 3 of the License, or
|
|
11
|
+
any later version.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
BORIS is distributed in the hope that it will be useful,
|
|
13
14
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
15
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
16
|
GNU General Public License for more details.
|
|
16
17
|
|
|
17
18
|
You should have received a copy of the GNU General Public License
|
|
18
|
-
along with this program; if not
|
|
19
|
-
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
20
|
-
MA 02110-1301, USA.
|
|
19
|
+
along with this program; if not see <http://www.gnu.org/licenses/>.
|
|
21
20
|
|
|
22
21
|
"""
|
|
23
22
|
|
|
24
23
|
import logging
|
|
25
|
-
from typing import Tuple
|
|
26
24
|
|
|
27
25
|
from PySide6.QtCore import Qt
|
|
28
26
|
from PySide6.QtWidgets import QAbstractItemView
|
|
@@ -32,7 +30,7 @@ from . import gui_utilities, observations_list, project_functions
|
|
|
32
30
|
from . import utilities as util
|
|
33
31
|
|
|
34
32
|
|
|
35
|
-
def select_observations2(self, mode: str, windows_title: str = "") ->
|
|
33
|
+
def select_observations2(self, mode: str, windows_title: str = "") -> tuple[str, list[str]]:
|
|
36
34
|
"""
|
|
37
35
|
allow user to select observations
|
|
38
36
|
mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1
|
|
@@ -149,37 +147,37 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
149
147
|
obsList.mode = mode
|
|
150
148
|
|
|
151
149
|
if mode == cfg.OPEN:
|
|
152
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
150
|
+
obsList.view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
153
151
|
obsList.pbOpen.setVisible(True)
|
|
154
152
|
|
|
155
153
|
if mode == cfg.VIEW:
|
|
156
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
154
|
+
obsList.view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
157
155
|
obsList.pbView.setVisible(True)
|
|
158
156
|
|
|
159
157
|
if mode == cfg.EDIT:
|
|
160
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
158
|
+
obsList.view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
161
159
|
obsList.pbEdit.setVisible(True)
|
|
162
160
|
|
|
163
161
|
if mode == cfg.SINGLE:
|
|
164
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
162
|
+
obsList.view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
165
163
|
obsList.pbOpen.setVisible(True)
|
|
166
164
|
obsList.pbView.setVisible(True)
|
|
167
165
|
obsList.pbEdit.setVisible(True)
|
|
168
166
|
|
|
169
167
|
if mode == cfg.MULTIPLE:
|
|
170
|
-
obsList.view.setSelectionMode(QAbstractItemView.MultiSelection)
|
|
168
|
+
obsList.view.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
|
171
169
|
obsList.pbOk.setVisible(True)
|
|
172
170
|
obsList.pbSelectAll.setVisible(True)
|
|
173
171
|
obsList.pbUnSelectAll.setVisible(True)
|
|
174
172
|
|
|
175
173
|
if mode == cfg.SELECT1:
|
|
176
|
-
obsList.view.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
174
|
+
obsList.view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
177
175
|
obsList.pbOk.setVisible(True)
|
|
178
176
|
|
|
179
177
|
# restore window geometry
|
|
180
178
|
gui_utilities.restore_geometry(obsList, "observations list", (900, 600))
|
|
181
179
|
|
|
182
|
-
obsList.view.sortItems(0, Qt.AscendingOrder)
|
|
180
|
+
obsList.view.sortItems(0, Qt.SortOrder.AscendingOrder)
|
|
183
181
|
for row in range(obsList.view.rowCount()):
|
|
184
182
|
obsList.view.resizeRowToContents(row)
|
|
185
183
|
|
|
@@ -196,15 +194,16 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
196
194
|
if idx.column() == 0: # first column
|
|
197
195
|
selected_observations.append(idx.data())
|
|
198
196
|
|
|
197
|
+
result_str: str = ""
|
|
199
198
|
if result == 0: # cancel
|
|
200
|
-
|
|
199
|
+
result_str = ""
|
|
201
200
|
if result == 1: # select
|
|
202
|
-
|
|
201
|
+
result_str = "ok"
|
|
203
202
|
if result == 2: # open
|
|
204
|
-
|
|
203
|
+
result_str = cfg.OPEN
|
|
205
204
|
if result == 3: # edit
|
|
206
|
-
|
|
205
|
+
result_str = cfg.EDIT
|
|
207
206
|
if result == 4: # view
|
|
208
|
-
|
|
207
|
+
result_str = cfg.VIEW
|
|
209
208
|
|
|
210
|
-
return
|
|
209
|
+
return result_str, selected_observations
|
boris/select_subj_behav.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -35,7 +35,7 @@ from . import utilities as util
|
|
|
35
35
|
|
|
36
36
|
def choose_obs_subj_behav_category(
|
|
37
37
|
self,
|
|
38
|
-
selected_observations: list,
|
|
38
|
+
selected_observations: list[str],
|
|
39
39
|
start_coding: Optional[dec] = dec("NaN"), # Union[..., None]
|
|
40
40
|
end_coding: Optional[dec] = dec("NaN"),
|
|
41
41
|
start_interval: Optional[dec] = dec("NaN"),
|
|
@@ -246,8 +246,8 @@ def choose_obs_subj_behav_category(
|
|
|
246
246
|
None,
|
|
247
247
|
cfg.programName,
|
|
248
248
|
"The start time is after the end time",
|
|
249
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
250
|
-
QMessageBox.NoButton,
|
|
249
|
+
QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Default,
|
|
250
|
+
QMessageBox.StandardButton.NoButton,
|
|
251
251
|
)
|
|
252
252
|
return {cfg.SELECTED_SUBJECTS: [], cfg.SELECTED_BEHAVIORS: []}
|
|
253
253
|
|
boris/state_events.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/subjects_pad.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/synthetic_time_budget.py
CHANGED
boris/time_budget_functions.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/time_budget_widget.py
CHANGED
boris/transitions.py
CHANGED
boris/utilities.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/version.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -20,5 +20,5 @@ This file is part of BORIS.
|
|
|
20
20
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
__version__ = "9.
|
|
24
|
-
__version_date__ = "
|
|
23
|
+
__version__ = "9.8.2a1"
|
|
24
|
+
__version_date__ = "2026-01-26"
|
boris/video_equalizer.py
CHANGED
boris/video_operations.py
CHANGED
boris/view_df.py
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2026 Olivier Friard
|
|
5
|
+
|
|
6
|
+
This file is part of BORIS.
|
|
7
|
+
|
|
8
|
+
BORIS is free software; you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU General Public License as published by
|
|
10
|
+
the Free Software Foundation; either version 3 of the License, or
|
|
11
|
+
any later version.
|
|
12
|
+
|
|
13
|
+
BORIS is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU General Public License
|
|
19
|
+
along with this program; if not see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
"""
|
|
4
22
|
|
|
5
|
-
from . import config as cfg
|
|
6
23
|
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
from PySide6.QtCore import QAbstractTableModel, Qt
|
|
26
|
+
from PySide6.QtWidgets import QFileDialog, QWidget
|
|
27
|
+
|
|
28
|
+
from . import config as cfg
|
|
7
29
|
from . import dialog
|
|
30
|
+
from .view_df_ui import Ui_Form
|
|
8
31
|
|
|
32
|
+
flag_pyreadr_loaded: bool = False
|
|
9
33
|
try:
|
|
10
34
|
import pyreadr
|
|
11
35
|
|
boris/write_event.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boris-behav-obs
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.8.2
|
|
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
|
|
@@ -51,7 +51,7 @@ It provides also some analysis tools like time budget and some plotting function
|
|
|
51
51
|
<!-- The BO-RIS paper has more than [ citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications. -->
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
The BORIS paper has more than
|
|
54
|
+
The BORIS paper has more than 2491 citations in peer-reviewed scientific publications.
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
boris/__init__.py,sha256=Gl4_cpvnp6b34syaGntl7mX1lbgMbMwoiFh-vx6JfbI,773
|
|
2
|
+
boris/__main__.py,sha256=TOrVDXimU9zCidmkyDJhceI-6r-CSRZ3J6PhvxjgE5k,764
|
|
3
|
+
boris/about.py,sha256=U8PgEPQwybWhGrmyWJtpjCM2xnsc9rVfUGcIHIhzrFQ,5460
|
|
4
|
+
boris/add_modifier.py,sha256=N5OujNy8UuKjDw0OVvaFG0qvj2qoo8BhlxHdYV4SRvc,26240
|
|
5
|
+
boris/add_modifier_ui.py,sha256=5PbyQKCrMhLXYkOqOvqis1E_oCYQI_994-uofIIIb0k,12366
|
|
6
|
+
boris/advanced_event_filtering.py,sha256=53rBYeycrDjiVLivtG3Y1Vbs5Q2Sd0JKnPwk92rfje4,15469
|
|
7
|
+
boris/behav_coding_map_creator.py,sha256=eJ3Lhf1r7x_Blkt7FK6dLMv_U9xcWb3oPsHWaRcVbbE,38797
|
|
8
|
+
boris/behavior_binary_table.py,sha256=tmk-4dzkTai9DMf70WenRnEtaUhpa1CeNiFnUNseVnI,12453
|
|
9
|
+
boris/behaviors_coding_map.py,sha256=HPo9onT7n12Qz8asEEfvDkwamI77RiQ_CZC1Ey384P8,7302
|
|
10
|
+
boris/boris_cli.py,sha256=Sc3B6FPRYJ93RGHr_dFQfqLiRR2mqh9qMYyyN226DvQ,13193
|
|
11
|
+
boris/cmd_arguments.py,sha256=es6AAL2JYXNt1pv6ygLtg5WzvfLix_zsJOQCMZ4WWYM,2065
|
|
12
|
+
boris/coding_pad.py,sha256=OaZmoHwkPy9uHmE0hkNOBJF8x3qRrM4trGr7v-zCN18,10978
|
|
13
|
+
boris/config.py,sha256=gKCg80wEuj6Y8P8ygXgDgJO-C-Lg-MBNBmyOpc7kaXc,18349
|
|
14
|
+
boris/config_file.py,sha256=j9NaoYfPKp6N3YqgFUWhc15QNkXlAWP8HUWh8XiRrcE,13534
|
|
15
|
+
boris/connections.py,sha256=kuZIEUjHaESbaNk-YD0ND_472gQM8Q85nId_NOU37As,19875
|
|
16
|
+
boris/converters.py,sha256=x094f5xdxkS0rrX6b5raYYAgEbvT-MZ6bdDnB2voDcI,11678
|
|
17
|
+
boris/converters_ui.py,sha256=Azd7DGDFVcpYD5zheHcYPIFTggUV2exNfw6LN0AXN4A,9925
|
|
18
|
+
boris/cooccurence.py,sha256=wBdjmiZWD_K06cp7bvTSdb5ZP5vbzj_88RzRbmBT_Lc,10367
|
|
19
|
+
boris/core.py,sha256=819EdvR-V-wWwQINVEC5qEYIt53blgEuK5Rwbb8igJ4,235104
|
|
20
|
+
boris/core_qrc.py,sha256=2vAMpM8WDUT5ZVTdrQ9DAN-ttdP5jiM9Cboqw48fNUk,638909
|
|
21
|
+
boris/core_ui.py,sha256=HgX2I2RB_8Mhk_6aW3fHvw29zo4gJQKbhDDsYi2zqiQ,77441
|
|
22
|
+
boris/db_functions.py,sha256=z1WIYrdutfInGFRnxTiEWVs8rOzSHB8K3u7VK-crusQ,13072
|
|
23
|
+
boris/dev.py,sha256=9pUElbjl9g17rFUJXX5aVSu55_iIKIuDxNdrB0DI_d0,3671
|
|
24
|
+
boris/dialog.py,sha256=pPbkJ49wNj5llhkWavQN7Noo6PQYg_xc-3tM-3glptQ,34154
|
|
25
|
+
boris/duration_widget.py,sha256=GjZgCAMGOcsNjoPiRImEVe6yMkH2vuNoh44ulpd5nlg,6924
|
|
26
|
+
boris/edit_event.py,sha256=ftzOpkGspbXM1yguayNseCuTZZx0rS-dWg_bb4FeqX4,7705
|
|
27
|
+
boris/edit_event_ui.py,sha256=qFgt00cejGB6UGC1mFkyZcsIAdvMeYMK0WYjZtJl1T0,9207
|
|
28
|
+
boris/event_operations.py,sha256=JpdbnR4lt-OGa8QGDM8lHly73b5hj5RrEi6V1IhitoU,40743
|
|
29
|
+
boris/events_cursor.py,sha256=IMG9NpF3Av-whgt5qSc4SzKZumvnuUTMZNtgHhL2Vw4,2078
|
|
30
|
+
boris/events_snapshots.py,sha256=dUMTfNkCcQ48xVTADCDPY_3iOI0CgVFvDkio-ISIEcI,30056
|
|
31
|
+
boris/exclusion_matrix.py,sha256=foT0P8E9lX1F8hk2BE7ZLoL0vi9tXIAsUxAbLMpAEBY,5269
|
|
32
|
+
boris/export_events.py,sha256=fjeA0P4vA8abK-rYetSmLymHI2kAzqFerDbQw6fBxqo,40113
|
|
33
|
+
boris/export_observation.py,sha256=vPUjiKrBcOr48LSAd_VR27q6FONbHAMZ3zT7dqtPvcA,50764
|
|
34
|
+
boris/external_processes.py,sha256=EPeoZXFLpMYNK-NA45QR3e8g1qFbii8wzE2KzJvmsPo,13607
|
|
35
|
+
boris/geometric_measurement.py,sha256=3q4bxuvvYG51WP33MEoJum1ecUVGmkNDiyZo1T85j3c,35049
|
|
36
|
+
boris/gui_utilities.py,sha256=TGx2nx3xzL8ZV9fhE0-728EK4HGRbtrCCnXNHqgL-MI,5458
|
|
37
|
+
boris/image_overlay.py,sha256=Qa_x9AZ4yeMiOUbtx6HTpyIIzcaSlOaviomm5TkgMFM,2527
|
|
38
|
+
boris/import_observations.py,sha256=Bxl5KgF2QulsOF53SUSx65rm5UTQFhejUn_vu-cVfKg,9074
|
|
39
|
+
boris/ipc_mpv.py,sha256=tJWdpd5_3wvyjnOiNihyhAaVTn_--Vsq7vo4XOQzWk8,9925
|
|
40
|
+
boris/irr.py,sha256=g4CGewXpDn8zIyChT34GSOTi1VNJPXHkwEw43crayWs,22335
|
|
41
|
+
boris/latency.py,sha256=tcgPPgWZpPWt6iAusiozB4QJwh0KJ2lfuRxnY_rK8Hw,9739
|
|
42
|
+
boris/measurement_widget.py,sha256=74GG-SVDQlnx2OhTtqiCq5AA1Xjfste_ghAN1rPmjJ0,4480
|
|
43
|
+
boris/media_file.py,sha256=rGMYYbVgShqHHSYZgtafJaavyFnfKha8lKn5_30nWFM,4818
|
|
44
|
+
boris/menu_options.py,sha256=mo1d_3IvOhT7yGsCdmNmIweOwY41kGkPplTospwCn4Q,7036
|
|
45
|
+
boris/modifier_coding_map_creator.py,sha256=8qOCrA3p4JFFdWYOPzeJ6IIb4h5cpgdypXwU2tzRLZc,33243
|
|
46
|
+
boris/modifiers_coding_map.py,sha256=oqOhqYaVQf_6pxU6xGD-JtbXjV-i1v5VlbxP51t9-nk,4554
|
|
47
|
+
boris/mpv.py,sha256=EfzIHjPbgewG4w3smEtqEUPZoVwYmMQkL4Q8ZyW-a58,76410
|
|
48
|
+
boris/mpv2.py,sha256=IUI4t4r9GYX7G5OXTjd3RhMMOkDdfal_15buBgksLsk,92152
|
|
49
|
+
boris/observation.py,sha256=UijD_ay6bYX7Y6YaA4n-J4QWslHTxyvg5XQ_Y-WnvBc,57535
|
|
50
|
+
boris/observation_operations.py,sha256=lZt5G9_TXCdI9qGaeT9tr2vWbLRfuPUi5rGjMBnUNtU,106581
|
|
51
|
+
boris/observation_ui.py,sha256=DAnU94QBNvkLuHT6AxTwqSk_D_n6VUhSl8PexZv_dUk,33309
|
|
52
|
+
boris/observations_list.py,sha256=xmkr_EKzj7izWjp06zS2speknS12AbVavxxpI-IAS4c,10591
|
|
53
|
+
boris/otx_parser.py,sha256=i7M0A5a0uoo6YTcTvlmnWxw9dh69GNc5Cfv0wce9UAg,16367
|
|
54
|
+
boris/param_panel.py,sha256=2RuxzfUw6MOqME-slrEEpv0xsm8JAMtasRY0KuoGnn0,8695
|
|
55
|
+
boris/param_panel_ui.py,sha256=4emQDFmuL4_R7bKxosLjdUb-VSPWkDm7suy38F5EKcA,13260
|
|
56
|
+
boris/player_dock_widget.py,sha256=X4sZwAdZ2WzAh8VfHLMGripl9k53g1qIw7koa4OlYk4,5880
|
|
57
|
+
boris/plot_data_module.py,sha256=a7XclqFWNzThFUQr3mGkG5iCvAL0TcoaRVUPRvELmvo,16594
|
|
58
|
+
boris/plot_events.py,sha256=FN9a0b2PhGJiEIxY-NEa75O9kQMtZsAviJVMOAw4BNs,24059
|
|
59
|
+
boris/plot_events_rt.py,sha256=7SISR0id3M-FtBngWnskUHUkj1nck7I6LRZqjGVZ6VQ,8326
|
|
60
|
+
boris/plot_spectrogram_rt.py,sha256=AFJS4NDoWLdZNKdNF-EfDSP6Ur_LY3tlms2q459yGsY,9611
|
|
61
|
+
boris/plot_waveform_rt.py,sha256=VbZBSTKMPIrTJM6Pb2PjOPZJBUFAlmX48Ru3M9odabE,7595
|
|
62
|
+
boris/plugins.py,sha256=SsTCOVnv83GhmUqwe-01dNCeyElnHSF8-a44TtoZO3w,17234
|
|
63
|
+
boris/preferences.py,sha256=fwqlZjx6hx6EfQW16sBg2VmklnS3BSUTLOGzsQK0M50,23451
|
|
64
|
+
boris/preferences_ui.py,sha256=gy0Ilxzlexdz9A6CSNto9uYdUrzNzNZ8RK-534cnfBA,36620
|
|
65
|
+
boris/project.py,sha256=IA-vfCF2StUBFoz7y1QSVI4AZFhtmp_rQAcg-0t2Bo4,86438
|
|
66
|
+
boris/project_functions.py,sha256=71vBM1LIBD3nZWiTSoBxSnIu4PtshPJyGDdNWUNhbXs,83549
|
|
67
|
+
boris/project_import_export.py,sha256=GtgGFzE7-RBXwFgtmL7bEzMqjcj6GorVtDNyO_ETFxs,38560
|
|
68
|
+
boris/project_ui.py,sha256=yB-ewhHt8S8DTTRIk-dNK2tPMNU24lNji9fDW_Xazu8,38805
|
|
69
|
+
boris/qrc_boris.py,sha256=aH-qUirYY1CGxmTK1SFCPvuZfazIHX4DdUKF1gxZeYM,675008
|
|
70
|
+
boris/qrc_boris5.py,sha256=prnOw7VGXWXRuVCYp_yIrmWhrlG1F9rx-3BQvkPenjY,161608
|
|
71
|
+
boris/select_modifiers.py,sha256=TdhOpnbp8Vpgkmhc-17C0eV7HG6Cf9oEubwMq6tq-oM,13319
|
|
72
|
+
boris/select_observations.py,sha256=NhJmzCtYV3ay7sB0NeDTHX6frOutgUDuyDAY9T6Tq0o,8054
|
|
73
|
+
boris/select_subj_behav.py,sha256=Y49lCXidxQ1SWzTQfvsr1xgMzJiq_7OisPLE2V5bpQM,11792
|
|
74
|
+
boris/state_events.py,sha256=XXF-v9re7wJZ6gVyI0GmYkd9hbtmunuQAydVL4QcJPU,7790
|
|
75
|
+
boris/subjects_pad.py,sha256=7VBrvmBvcL9iH_5ECmht0aR3iKUvKuOAa3EcX8_IHNU,3543
|
|
76
|
+
boris/synthetic_time_budget.py,sha256=K5HIo7IMJZ5g3WrTY3ugYRAM3dDCE_i1mqutfgnB9Jk,10489
|
|
77
|
+
boris/time_budget_functions.py,sha256=fK5VSlrfAR7eBTD690IBZiJ8p5Xq3rbu-3V1R6So69o,52186
|
|
78
|
+
boris/time_budget_widget.py,sha256=BxG0tnRPQV8QI-r3W5JhmcdiwzxtXJOdUURjdV_Ba0E,43213
|
|
79
|
+
boris/transitions.py,sha256=A7JQq8vB2u4GrZjt1hGD0EHmk1fn4ynx3HGNMHZQ7hU,12246
|
|
80
|
+
boris/utilities.py,sha256=sRBy1pTopG42nhdcsRCvioOpCp-KyfjOnkOwlUXSnB0,58805
|
|
81
|
+
boris/version.py,sha256=G5zqSSz0KwLrHT3UiLe2bIGMJch6IsOGLaO8EiD0Zqk,789
|
|
82
|
+
boris/video_equalizer.py,sha256=l4SrrfZT65IN0yLKHnHLe48PLtn0yS_1q-mgFZr1mak,5823
|
|
83
|
+
boris/video_equalizer_ui.py,sha256=1CG3s79eM4JAbaCx3i1ILZXLceb41_gGXlOLNfpBgnw,10142
|
|
84
|
+
boris/video_operations.py,sha256=G2fz9JijuebM5LYx4e9YmR-kb05w3-2DKoIZoE2iZbs,11054
|
|
85
|
+
boris/view_df.py,sha256=ddbpLV3sjIjBB8pCM94D5I2hdjlLeaN6c5AnNLoUp6w,4106
|
|
86
|
+
boris/view_df_ui.py,sha256=CaMeRH_vQ00CTDDFQn73ZZaS-r8BSTWpL-dMCFqzJ_Q,2775
|
|
87
|
+
boris/write_event.py,sha256=JaOkH9ZVSilDX8PACJB-dkLjmEIi6zm_05C9wRKEdlc,23817
|
|
88
|
+
boris/analysis_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
|
+
boris/analysis_plugins/_latency.py,sha256=9kCdFDtb5Zdao1xFpioi_exm_IxyGm6RlY3Opn6GUFo,2030
|
|
90
|
+
boris/analysis_plugins/export_to_feral.py,sha256=Icho_3e-2Y9Z1i_56TxnYigxsoWcRHm14XzE55p3nNM,11428
|
|
91
|
+
boris/analysis_plugins/irr_cohen_kappa.py,sha256=OqmivIE6i1hTcFVMp0EtY0Sr7C1Jm9t0D4IKbDfTJ7U,4268
|
|
92
|
+
boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py,sha256=DtzFLRToR9GdkmWYDcCmpSvxHGpguVp-_n8F-t7ND7c,4461
|
|
93
|
+
boris/analysis_plugins/irr_weighted_cohen_kappa.py,sha256=RX2jI4hgLU1aBkzIWqY3owZcqpMzX8DpOsrRvaIFARg,6435
|
|
94
|
+
boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py,sha256=DXrvac4p3_jzakHi_hTW8ZG9rniGo4YCyUH_jJ7fz8c,6744
|
|
95
|
+
boris/analysis_plugins/list_of_dataframe_columns.py,sha256=VEiVhxADtyaIKN4JrfFV02TuTAfWhQ60bf1mHVQp27I,437
|
|
96
|
+
boris/analysis_plugins/number_of_occurences.py,sha256=IDyDrdezqvSKT3BlD8QWpSYk8X9nnBBLI80OUnFJ3bY,509
|
|
97
|
+
boris/analysis_plugins/number_of_occurences_by_independent_variable.py,sha256=_7HTKXsyxNfyO69tP8zkQEHzT0C7qHdL1sqBjnUfRQY,1459
|
|
98
|
+
boris/analysis_plugins/time_budget.py,sha256=bzOS0FUJ7P2kQ9d-TxXq2_OCpo-MngGDFeU5aKsniDQ,2188
|
|
99
|
+
boris/portion/__init__.py,sha256=KmMu4q-iIFO5nrVRIP340mEzxzOlZQqaRupiDmS70es,642
|
|
100
|
+
boris/portion/const.py,sha256=JSYZUktIPCekB6qSom1FPfASn-X7w0G5-aNNHKhIZnw,1715
|
|
101
|
+
boris/portion/dict.py,sha256=uNM-LEY52CZ2VNMMW_C9QukoyTvPlQf8vcbGa1lQBHI,11281
|
|
102
|
+
boris/portion/func.py,sha256=mSQr20YS1ug7R1fRqBg8LifjtXDRvJ6Kjc3WOeL9P34,2172
|
|
103
|
+
boris/portion/interval.py,sha256=sOlj3MAGGaB-JxCkigS-n3qw0fY7TANAsXv1pavr8J4,19931
|
|
104
|
+
boris/portion/io.py,sha256=kpq44pw3xnIyAlPwaR5qRHKRdZ72f8HS9YVIWs5k2pk,6367
|
|
105
|
+
boris_behav_obs-9.8.2.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
|
|
106
|
+
boris_behav_obs-9.8.2.dist-info/METADATA,sha256=q_scBBgdc6GEQy6iNiM7iNBdu9bjwpumjUH1BK127Tk,5203
|
|
107
|
+
boris_behav_obs-9.8.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
108
|
+
boris_behav_obs-9.8.2.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
|
|
109
|
+
boris_behav_obs-9.8.2.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
|
|
110
|
+
boris_behav_obs-9.8.2.dist-info/RECORD,,
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
BORIS plugin
|
|
3
|
-
|
|
4
|
-
Export to FERAL (getferal.ai)
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import pandas as pd
|
|
8
|
-
import json
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
|
|
11
|
-
from PySide6.QtWidgets import QFileDialog
|
|
12
|
-
|
|
13
|
-
# dependencies for CategoryDialog
|
|
14
|
-
from PySide6.QtWidgets import QListWidget, QListWidgetItem, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QDialog
|
|
15
|
-
from PySide6.QtCore import Qt
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
__version__ = "0.1.1"
|
|
19
|
-
__version_date__ = "2025-11-28"
|
|
20
|
-
__plugin_name__ = "Export observations to FERAL"
|
|
21
|
-
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class CategoryDialog(QDialog):
|
|
25
|
-
def __init__(self, items, parent=None):
|
|
26
|
-
super().__init__(parent)
|
|
27
|
-
|
|
28
|
-
self.setWindowTitle("Organize the videos in categories")
|
|
29
|
-
|
|
30
|
-
self.setModal(True)
|
|
31
|
-
|
|
32
|
-
# Main layout
|
|
33
|
-
main_layout = QVBoxLayout(self)
|
|
34
|
-
lists_layout = QHBoxLayout()
|
|
35
|
-
|
|
36
|
-
# All videos
|
|
37
|
-
self.list_unclassified = self._create_list_widget()
|
|
38
|
-
self.label_unclassified = QLabel("All videos")
|
|
39
|
-
col0_layout = QVBoxLayout()
|
|
40
|
-
col0_layout.addWidget(self.label_unclassified)
|
|
41
|
-
col0_layout.addWidget(self.list_unclassified)
|
|
42
|
-
|
|
43
|
-
self.list_cat1 = self._create_list_widget()
|
|
44
|
-
self.label_cat1 = QLabel("train")
|
|
45
|
-
col1_layout = QVBoxLayout()
|
|
46
|
-
col1_layout.addWidget(self.label_cat1)
|
|
47
|
-
col1_layout.addWidget(self.list_cat1)
|
|
48
|
-
|
|
49
|
-
self.list_cat2 = self._create_list_widget()
|
|
50
|
-
self.label_cat2 = QLabel("val")
|
|
51
|
-
col2_layout = QVBoxLayout()
|
|
52
|
-
col2_layout.addWidget(self.label_cat2)
|
|
53
|
-
col2_layout.addWidget(self.list_cat2)
|
|
54
|
-
|
|
55
|
-
self.list_cat3 = self._create_list_widget()
|
|
56
|
-
self.label_cat3 = QLabel("test")
|
|
57
|
-
col3_layout = QVBoxLayout()
|
|
58
|
-
col3_layout.addWidget(self.label_cat3)
|
|
59
|
-
col3_layout.addWidget(self.list_cat3)
|
|
60
|
-
|
|
61
|
-
self.list_cat4 = self._create_list_widget()
|
|
62
|
-
self.label_cat4 = QLabel("inference")
|
|
63
|
-
col4_layout = QVBoxLayout()
|
|
64
|
-
col4_layout.addWidget(self.label_cat4)
|
|
65
|
-
col4_layout.addWidget(self.list_cat4)
|
|
66
|
-
|
|
67
|
-
# Add all columns to the horizontal layout
|
|
68
|
-
lists_layout.addLayout(col0_layout)
|
|
69
|
-
lists_layout.addLayout(col1_layout)
|
|
70
|
-
lists_layout.addLayout(col2_layout)
|
|
71
|
-
lists_layout.addLayout(col3_layout)
|
|
72
|
-
lists_layout.addLayout(col4_layout)
|
|
73
|
-
|
|
74
|
-
main_layout.addLayout(lists_layout)
|
|
75
|
-
|
|
76
|
-
buttons_layout = QHBoxLayout()
|
|
77
|
-
self.btn_ok = QPushButton("OK")
|
|
78
|
-
self.btn_cancel = QPushButton("Cancel")
|
|
79
|
-
|
|
80
|
-
self.btn_ok.clicked.connect(self.accept)
|
|
81
|
-
self.btn_cancel.clicked.connect(self.reject)
|
|
82
|
-
|
|
83
|
-
buttons_layout.addStretch()
|
|
84
|
-
buttons_layout.addWidget(self.btn_ok)
|
|
85
|
-
buttons_layout.addWidget(self.btn_cancel)
|
|
86
|
-
|
|
87
|
-
main_layout.addLayout(buttons_layout)
|
|
88
|
-
|
|
89
|
-
# Populate "Unclassified" with input items
|
|
90
|
-
for text in items:
|
|
91
|
-
QListWidgetItem(text, self.list_unclassified)
|
|
92
|
-
|
|
93
|
-
def _create_list_widget(self):
|
|
94
|
-
"""
|
|
95
|
-
Create a QListWidget ready for drag & drop.
|
|
96
|
-
"""
|
|
97
|
-
lw = QListWidget()
|
|
98
|
-
lw.setSelectionMode(QListWidget.ExtendedSelection)
|
|
99
|
-
lw.setDragEnabled(True)
|
|
100
|
-
lw.setAcceptDrops(True)
|
|
101
|
-
lw.setDropIndicatorShown(True)
|
|
102
|
-
lw.setDragDropMode(QListWidget.DragDrop)
|
|
103
|
-
lw.setDefaultDropAction(Qt.MoveAction)
|
|
104
|
-
return lw
|
|
105
|
-
|
|
106
|
-
def get_categories(self):
|
|
107
|
-
"""
|
|
108
|
-
Return the content of all categories as a dictionary of lists.
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
def collect(widget):
|
|
112
|
-
return [widget.item(i).text().rstrip("*") for i in range(widget.count())]
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
"unclassified": collect(self.list_unclassified),
|
|
116
|
-
"train": collect(self.list_cat1),
|
|
117
|
-
"val": collect(self.list_cat2),
|
|
118
|
-
"test": collect(self.list_cat3),
|
|
119
|
-
"inference": collect(self.list_cat4),
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def run(df: pd.DataFrame, project: dict):
|
|
124
|
-
"""
|
|
125
|
-
Export observations to FERAL
|
|
126
|
-
See https://www.getferal.ai/ > Label Preparation
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
out: dict = {
|
|
130
|
-
"is_multilabel": False,
|
|
131
|
-
"splits": {
|
|
132
|
-
"train": [],
|
|
133
|
-
"val": [],
|
|
134
|
-
"test": [],
|
|
135
|
-
"inference": [],
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
log: list = []
|
|
140
|
-
|
|
141
|
-
# class names
|
|
142
|
-
class_names = {x: project["behaviors_conf"][x]["code"] for x in project["behaviors_conf"]}
|
|
143
|
-
out["class_names"] = class_names
|
|
144
|
-
reversed_class_names = {project["behaviors_conf"][x]["code"]: int(x) for x in project["behaviors_conf"]}
|
|
145
|
-
log.append(f"{class_names=}")
|
|
146
|
-
|
|
147
|
-
observations: list = sorted([x for x in project["observations"]])
|
|
148
|
-
log.append(f"Selected observation: {observations}")
|
|
149
|
-
|
|
150
|
-
labels: dict = {}
|
|
151
|
-
video_list: list = []
|
|
152
|
-
for observation_id in observations:
|
|
153
|
-
log.append("---")
|
|
154
|
-
log.append(observation_id)
|
|
155
|
-
|
|
156
|
-
# check number of media file in player #1
|
|
157
|
-
if len(project["observations"][observation_id]["file"]["1"]) != 1:
|
|
158
|
-
log.append(f"The observation {observation_id} contains more than one video")
|
|
159
|
-
continue
|
|
160
|
-
|
|
161
|
-
# check number of coded subjects
|
|
162
|
-
if len(set([x[1] for x in project["observations"][observation_id]["events"]])) > 1:
|
|
163
|
-
log.append(f"The observation {observation_id} contains more than one subject")
|
|
164
|
-
continue
|
|
165
|
-
|
|
166
|
-
media_file_path: str = project["observations"][observation_id]["file"]["1"][0]
|
|
167
|
-
media_file_name = str(Path(media_file_path).name)
|
|
168
|
-
|
|
169
|
-
# skip if no events
|
|
170
|
-
if not project["observations"][observation_id]["events"]:
|
|
171
|
-
video_list.append(media_file_name)
|
|
172
|
-
log.append(f"No events for observation {observation_id}")
|
|
173
|
-
continue
|
|
174
|
-
else:
|
|
175
|
-
video_list.append(media_file_name + "*")
|
|
176
|
-
|
|
177
|
-
# extract FPS
|
|
178
|
-
FPS = project["observations"][observation_id]["media_info"]["fps"][media_file_path]
|
|
179
|
-
log.append(f"{media_file_name} {FPS=}")
|
|
180
|
-
# extract media duration
|
|
181
|
-
duration = project["observations"][observation_id]["media_info"]["length"][media_file_path]
|
|
182
|
-
log.append(f"{media_file_name} {duration=}")
|
|
183
|
-
|
|
184
|
-
number_of_frames = int(duration / (1 / FPS))
|
|
185
|
-
log.append(f"{number_of_frames=}")
|
|
186
|
-
|
|
187
|
-
labels[media_file_name] = [0] * number_of_frames
|
|
188
|
-
|
|
189
|
-
for idx in range(number_of_frames):
|
|
190
|
-
t = idx * (1 / FPS)
|
|
191
|
-
behaviors = (
|
|
192
|
-
df[(df["Observation id"] == observation_id) & (df["Start (s)"] <= t) & (df["Stop (s)"] >= t)]["Behavior"].unique().tolist()
|
|
193
|
-
)
|
|
194
|
-
if len(behaviors) > 1:
|
|
195
|
-
log.append(f"The observation {observation_id} contains more than one behavior for frame {idx}")
|
|
196
|
-
del labels[media_file_name]
|
|
197
|
-
break
|
|
198
|
-
if behaviors:
|
|
199
|
-
behaviors_idx = reversed_class_names[behaviors[0]]
|
|
200
|
-
labels[media_file_name][idx] = behaviors_idx
|
|
201
|
-
|
|
202
|
-
out["labels"] = labels
|
|
203
|
-
|
|
204
|
-
# splits
|
|
205
|
-
dlg = CategoryDialog(video_list)
|
|
206
|
-
|
|
207
|
-
if dlg.exec(): # Dialog accepted
|
|
208
|
-
result = dlg.get_categories()
|
|
209
|
-
del result["unclassified"]
|
|
210
|
-
out["splits"] = result
|
|
211
|
-
|
|
212
|
-
filename, _ = QFileDialog.getSaveFileName(
|
|
213
|
-
None,
|
|
214
|
-
"Choose a file to save",
|
|
215
|
-
"", # start directory
|
|
216
|
-
"JSON files (*.json);;All files (*.*)",
|
|
217
|
-
)
|
|
218
|
-
if filename:
|
|
219
|
-
with open(filename, "w") as f_out:
|
|
220
|
-
f_out.write(json.dumps(out, separators=(",", ": "), indent=1))
|
|
221
|
-
|
|
222
|
-
else:
|
|
223
|
-
log.append("splits section missing")
|
|
224
|
-
|
|
225
|
-
return "\n".join(log)
|