boris-behav-obs 9.0.2__py2.py3-none-any.whl → 9.0.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/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +2 -2
- boris/add_modifier.py +1 -1
- boris/add_modifier_ui.py +22 -22
- boris/advanced_event_filtering.py +1 -1
- boris/analysis_plugins/number_of_occurences.py +6 -3
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +6 -4
- boris/analysis_plugins/time_budget.py +24 -14
- 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 +16 -9
- boris/config_file.py +1 -1
- boris/connections.py +1 -1
- boris/converters.py +1 -1
- boris/cooccurence.py +1 -1
- boris/core.py +144 -176
- boris/db_functions.py +1 -1
- boris/dialog.py +10 -277
- boris/edit_event.py +8 -1
- boris/event_operations.py +1 -1
- boris/events_cursor.py +1 -1
- boris/events_snapshots.py +1 -1
- boris/exclusion_matrix.py +1 -1
- boris/export_events.py +1 -1
- 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/irr.py +1 -1
- boris/latency.py +1 -1
- boris/map_creator.py +1 -1
- boris/measurement_widget.py +1 -1
- boris/media_file.py +1 -1
- boris/menu_options.py +1 -1
- boris/modifiers_coding_map.py +1 -1
- boris/observation.py +1 -1
- boris/observation_operations.py +530 -425
- boris/observations_list.py +3 -3
- boris/otx_parser.py +1 -1
- boris/param_panel.py +1 -1
- boris/player_dock_widget.py +13 -9
- 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 +1 -1
- boris/plot_waveform_rt.py +1 -1
- boris/plugins.py +2 -2
- boris/preferences.py +6 -1
- boris/preferences_ui.py +7 -1
- boris/project.py +1 -1
- boris/project_functions.py +73 -76
- boris/project_import_export.py +1 -1
- boris/select_modifiers.py +1 -1
- boris/select_observations.py +6 -6
- boris/select_subj_behav.py +1 -1
- 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 +5 -5
- 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/write_event.py +1 -1
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.5.dist-info}/METADATA +1 -1
- boris_behav_obs-9.0.5.dist-info/RECORD +103 -0
- boris_behav_obs-9.0.2.dist-info/RECORD +0 -103
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.5.dist-info}/LICENSE.TXT +0 -0
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.5.dist-info}/WHEEL +0 -0
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.5.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.5.dist-info}/top_level.txt +0 -0
boris/core.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -34,6 +34,7 @@ import json
|
|
|
34
34
|
import logging
|
|
35
35
|
import pathlib as pl
|
|
36
36
|
import platform
|
|
37
|
+
import importlib
|
|
37
38
|
import re
|
|
38
39
|
import PIL.Image
|
|
39
40
|
import PIL.ImageEnhance
|
|
@@ -116,6 +117,7 @@ from . import config_file
|
|
|
116
117
|
from . import select_subj_behav
|
|
117
118
|
from . import observation_operations
|
|
118
119
|
from . import write_event
|
|
120
|
+
from . import view_df
|
|
119
121
|
|
|
120
122
|
|
|
121
123
|
# matplotlib.pyplot.switch_backend("Qt5Agg")
|
|
@@ -282,7 +284,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
282
284
|
plot_colors = cfg.BEHAVIORS_PLOT_COLORS
|
|
283
285
|
behav_category_colors = cfg.CATEGORY_COLORS_LIST
|
|
284
286
|
|
|
285
|
-
measurement_w = None
|
|
287
|
+
# measurement_w = None
|
|
286
288
|
current_image_size = None
|
|
287
289
|
|
|
288
290
|
media_scan_sampling_mem: list = []
|
|
@@ -311,7 +313,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
311
313
|
playerType: str = "" # cfg.MEDIA, cfg.LIVE, cfg.VIEWER
|
|
312
314
|
|
|
313
315
|
# spectrogram
|
|
314
|
-
chunk_length = 60 # spectrogram chunk length in seconds
|
|
316
|
+
chunk_length: float = 60 # spectrogram chunk length in seconds
|
|
315
317
|
|
|
316
318
|
close_the_same_current_event: bool = False
|
|
317
319
|
tcp_port: int = 0
|
|
@@ -323,7 +325,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
323
325
|
|
|
324
326
|
dw_player: list = []
|
|
325
327
|
|
|
326
|
-
save_project_json_started = False
|
|
328
|
+
save_project_json_started: bool = False
|
|
327
329
|
|
|
328
330
|
mem_hash_obs: int = 0
|
|
329
331
|
|
|
@@ -537,11 +539,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
537
539
|
media_file_available=ib.elements["Test media file accessibility"].isChecked(),
|
|
538
540
|
)
|
|
539
541
|
if msg:
|
|
540
|
-
msg = f"Some issues were found in the project<br><br>{msg}"
|
|
541
542
|
self.results = dialog.Results_dialog()
|
|
542
543
|
self.results.setWindowTitle("Check project integrity")
|
|
543
544
|
self.results.ptText.clear()
|
|
544
|
-
self.results.ptText.appendHtml(msg)
|
|
545
|
+
self.results.ptText.appendHtml(f"Some issues were found in the project<br><br>{msg}")
|
|
545
546
|
self.results.show()
|
|
546
547
|
else:
|
|
547
548
|
QMessageBox.information(self, cfg.programName, "The current project has no issues")
|
|
@@ -1649,9 +1650,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1649
1650
|
"""
|
|
1650
1651
|
|
|
1651
1652
|
if self.observationId and self.projectFileName:
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1653
|
+
if not self.save_project_activated():
|
|
1654
|
+
logging.info("project autosaved")
|
|
1655
|
+
else:
|
|
1656
|
+
logging.warning("Error autosaving project")
|
|
1655
1657
|
else:
|
|
1656
1658
|
logging.debug((f"project not autosaved: observation id: {self.observationId} project file name: {self.projectFileName}"))
|
|
1657
1659
|
|
|
@@ -1690,43 +1692,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1690
1692
|
break
|
|
1691
1693
|
return currentMedia, round(frameCurrentMedia)
|
|
1692
1694
|
|
|
1693
|
-
'''
|
|
1694
|
-
def extract_exif_DateTimeOriginal(self, file_path: str) -> int:
|
|
1695
|
-
"""
|
|
1696
|
-
extract the EXIF DateTimeOriginal tag
|
|
1697
|
-
return epoch time
|
|
1698
|
-
if the tag is not available return -1
|
|
1699
|
-
|
|
1700
|
-
Args:
|
|
1701
|
-
file_path (str): path of the media file
|
|
1702
|
-
|
|
1703
|
-
Returns:
|
|
1704
|
-
int: timestamp
|
|
1705
|
-
|
|
1706
|
-
"""
|
|
1707
|
-
try:
|
|
1708
|
-
with open(file_path, "rb") as f_in:
|
|
1709
|
-
tags = exifread.process_file(f_in, details=False, stop_tag="EXIF DateTimeOriginal")
|
|
1710
|
-
if "EXIF DateTimeOriginal" in tags:
|
|
1711
|
-
date_time_original = (
|
|
1712
|
-
f'{tags["EXIF DateTimeOriginal"].values[:4]}-'
|
|
1713
|
-
f'{tags["EXIF DateTimeOriginal"].values[5:7]}-'
|
|
1714
|
-
f'{tags["EXIF DateTimeOriginal"].values[8:10]} '
|
|
1715
|
-
f'{tags["EXIF DateTimeOriginal"].values.split(" ")[-1]}'
|
|
1716
|
-
)
|
|
1717
|
-
return int(datetime.datetime.strptime(date_time_original, "%Y-%m-%d %H:%M:%S").timestamp())
|
|
1718
|
-
else:
|
|
1719
|
-
try:
|
|
1720
|
-
# read from file name (YYYY-MM-DD_HHMMSS)
|
|
1721
|
-
return int(datetime.datetime.strptime(pl.Path(file_path).stem, "%Y-%m-%d_%H%M%S").timestamp())
|
|
1722
|
-
except Exception:
|
|
1723
|
-
# read from file name (YYYY-MM-DD_HH:MM:SS)
|
|
1724
|
-
return int(datetime.datetime.strptime(pl.Path(file_path).stem, "%Y-%m-%d_%H:%M:%S").timestamp())
|
|
1725
|
-
|
|
1726
|
-
except Exception:
|
|
1727
|
-
return -1
|
|
1728
|
-
'''
|
|
1729
|
-
|
|
1730
1695
|
def extract_frame(self, dw):
|
|
1731
1696
|
"""
|
|
1732
1697
|
for MEDIA obs: extract frame from video and visualize it in frame_viewer
|
|
@@ -1743,9 +1708,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1743
1708
|
)
|
|
1744
1709
|
|
|
1745
1710
|
if self.playerType == cfg.IMAGES:
|
|
1746
|
-
# print(f"{self.images_list=}")
|
|
1747
|
-
# print(f"{self.image_idx=}")
|
|
1748
|
-
|
|
1749
1711
|
if self.image_idx >= len(self.images_list):
|
|
1750
1712
|
QMessageBox.critical(
|
|
1751
1713
|
None,
|
|
@@ -1769,7 +1731,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1769
1731
|
else:
|
|
1770
1732
|
msg += "<br>EXIF Date/Time Original: <b>NA</b>"
|
|
1771
1733
|
|
|
1772
|
-
# self.image_time_ref = 0
|
|
1773
1734
|
if self.image_idx == 0 and date_time_original != -1:
|
|
1774
1735
|
self.image_time_ref = date_time_original
|
|
1775
1736
|
|
|
@@ -2257,47 +2218,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2257
2218
|
set_and_update_pan_and_zoom(pan_x=0, pan_y=0, zoom=0)
|
|
2258
2219
|
return
|
|
2259
2220
|
|
|
2260
|
-
# def read_tw_event_field(self, row_idx: int, player_type: str, field_type: str) -> Union[str, None, int, dec]:
|
|
2261
|
-
# """
|
|
2262
|
-
# return value of field for event in TW or NA if not available
|
|
2263
|
-
# """
|
|
2264
|
-
# if field_type not in cfg.TW_EVENTS_FIELDS[player_type]:
|
|
2265
|
-
# return None
|
|
2266
|
-
#
|
|
2267
|
-
# return self.twEvents.item(row_idx, cfg.TW_OBS_FIELD[player_type][field_type]).text()
|
|
2268
|
-
|
|
2269
|
-
# def configure_twevents_columns(self):
|
|
2270
|
-
# """
|
|
2271
|
-
# configure the visible columns of twEvent tablewidget
|
|
2272
|
-
# configuration for playerType is recorded in self.config_param[f"{self.playerType} tw fields"]
|
|
2273
|
-
# """
|
|
2274
|
-
|
|
2275
|
-
# dlg = dialog.Input_dialog(
|
|
2276
|
-
# label_caption="Select the columns to show",
|
|
2277
|
-
# elements_list=[
|
|
2278
|
-
# (
|
|
2279
|
-
# "cb",
|
|
2280
|
-
# x,
|
|
2281
|
-
# # default state
|
|
2282
|
-
# x
|
|
2283
|
-
# in self.config_param.get(
|
|
2284
|
-
# f"{self.playerType} tw fields",
|
|
2285
|
-
# cfg.TW_EVENTS_FIELDS[self.playerType],
|
|
2286
|
-
# ),
|
|
2287
|
-
# )
|
|
2288
|
-
# for x in cfg.TW_EVENTS_FIELDS[self.playerType]
|
|
2289
|
-
# ],
|
|
2290
|
-
# title="Select the column to show",
|
|
2291
|
-
# )
|
|
2292
|
-
# if not dlg.exec_():
|
|
2293
|
-
# return
|
|
2294
|
-
|
|
2295
|
-
# self.config_param[f"{self.playerType} tw fields"] = tuple(
|
|
2296
|
-
# field for field in cfg.TW_EVENTS_FIELDS[self.playerType] if dlg.elements[field].isChecked()
|
|
2297
|
-
# )
|
|
2298
|
-
|
|
2299
|
-
# self.load_tw_events(self.observationId)
|
|
2300
|
-
|
|
2301
2221
|
def configure_tvevents_columns(self):
|
|
2302
2222
|
"""
|
|
2303
2223
|
configure the visible columns of tv_events tableview
|
|
@@ -2405,7 +2325,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2405
2325
|
# self.table.setSortingEnabled(True)
|
|
2406
2326
|
# self.table.sortByColumn(0, Qt.AscendingOrder)
|
|
2407
2327
|
|
|
2408
|
-
def load_tw_events(self, obs_id):
|
|
2328
|
+
def load_tw_events(self, obs_id) -> None:
|
|
2409
2329
|
"""
|
|
2410
2330
|
load events in table view and update START/STOP
|
|
2411
2331
|
|
|
@@ -2531,76 +2451,77 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2531
2451
|
self.plot_data[pd].close_plot()
|
|
2532
2452
|
|
|
2533
2453
|
except Exception:
|
|
2534
|
-
|
|
2535
|
-
"""
|
|
2536
|
-
while self.plot_data:
|
|
2537
|
-
self.plot_data[0].close_plot()
|
|
2538
|
-
time.sleep(1)
|
|
2539
|
-
del self.plot_data[0]
|
|
2540
|
-
"""
|
|
2454
|
+
logging.warning("Error closing plot window")
|
|
2541
2455
|
|
|
2542
2456
|
if hasattr(self, "measurement_w"):
|
|
2543
2457
|
try:
|
|
2544
2458
|
self.measurement_w.close()
|
|
2545
|
-
del self.
|
|
2459
|
+
del self.measurement_w
|
|
2546
2460
|
except Exception:
|
|
2547
|
-
|
|
2461
|
+
logging.warning("Error closing measurement window")
|
|
2548
2462
|
|
|
2549
2463
|
if hasattr(self, "codingpad"):
|
|
2550
2464
|
try:
|
|
2551
2465
|
self.codingpad.close()
|
|
2552
2466
|
del self.codingpad
|
|
2553
2467
|
except Exception:
|
|
2554
|
-
|
|
2468
|
+
logging.warning("Error closing coding pad window")
|
|
2555
2469
|
|
|
2556
2470
|
if hasattr(self, "subjects_pad"):
|
|
2557
2471
|
try:
|
|
2558
2472
|
self.subjects_pad.close()
|
|
2559
2473
|
del self.subjects_pad
|
|
2560
2474
|
except Exception:
|
|
2561
|
-
|
|
2475
|
+
logging.warning("Error closing subjects pad window")
|
|
2562
2476
|
|
|
2563
2477
|
if hasattr(self, "spectro"):
|
|
2564
2478
|
try:
|
|
2565
2479
|
self.spectro.close()
|
|
2566
2480
|
del self.spectro
|
|
2567
2481
|
except Exception:
|
|
2568
|
-
|
|
2482
|
+
logging.warning("Error closing spectrogram window")
|
|
2569
2483
|
|
|
2570
2484
|
if hasattr(self, "waveform"):
|
|
2571
2485
|
try:
|
|
2572
2486
|
self.waveform.close()
|
|
2573
2487
|
del self.waveform
|
|
2574
2488
|
except Exception:
|
|
2575
|
-
|
|
2489
|
+
logging.warning("Error closing waveform window")
|
|
2576
2490
|
|
|
2577
2491
|
if hasattr(self, "plot_events"):
|
|
2578
2492
|
try:
|
|
2579
2493
|
self.plot_events.close()
|
|
2580
2494
|
del self.plot_events
|
|
2581
2495
|
except Exception:
|
|
2582
|
-
|
|
2496
|
+
logging.warning("Error closing plot events window")
|
|
2583
2497
|
|
|
2584
2498
|
if hasattr(self, "results"):
|
|
2585
2499
|
try:
|
|
2586
2500
|
self.results.close()
|
|
2587
2501
|
del self.results
|
|
2588
2502
|
except Exception:
|
|
2589
|
-
|
|
2503
|
+
logging.warning("Error closing results window")
|
|
2590
2504
|
|
|
2591
2505
|
if hasattr(self, "mapCreatorWindow"):
|
|
2592
2506
|
try:
|
|
2593
2507
|
self.mapCreatorWindow.close()
|
|
2594
2508
|
del self.mapCreatorWindow
|
|
2595
2509
|
except Exception:
|
|
2596
|
-
|
|
2510
|
+
logging.warning("Error closing map creator window")
|
|
2597
2511
|
|
|
2598
2512
|
if hasattr(self, "video_equalizer_wgt"):
|
|
2599
2513
|
try:
|
|
2600
2514
|
self.video_equalizer_wgt.close()
|
|
2601
2515
|
del self.video_equalizer_wgt
|
|
2602
2516
|
except Exception:
|
|
2603
|
-
|
|
2517
|
+
logging.warning("Error closing video equalizer window")
|
|
2518
|
+
|
|
2519
|
+
if hasattr(self, "view_dataframe"):
|
|
2520
|
+
try:
|
|
2521
|
+
self.view_dataframe.close()
|
|
2522
|
+
del self.view_dataframe
|
|
2523
|
+
except Exception:
|
|
2524
|
+
logging.warning("Error closing the plugin results window")
|
|
2604
2525
|
|
|
2605
2526
|
# delete behavior coding map
|
|
2606
2527
|
for idx in self.bcm_dict:
|
|
@@ -2671,22 +2592,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2671
2592
|
except Exception:
|
|
2672
2593
|
logging.debug("error in observation time interval")
|
|
2673
2594
|
|
|
2674
|
-
# TODO: replace by event_type in project_functions
|
|
2675
|
-
def eventType(self, code):
|
|
2676
|
-
"""
|
|
2677
|
-
returns type of event for code
|
|
2678
|
-
"""
|
|
2679
|
-
for idx in self.pj[cfg.ETHOGRAM]:
|
|
2680
|
-
if self.pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] == code:
|
|
2681
|
-
return self.pj[cfg.ETHOGRAM][idx][cfg.TYPE]
|
|
2682
|
-
return None
|
|
2683
|
-
|
|
2684
2595
|
def extract_observed_behaviors(self, selected_observations, selectedSubjects):
|
|
2685
2596
|
"""
|
|
2686
2597
|
extract unique behaviors codes from obs_id observation
|
|
2687
2598
|
"""
|
|
2688
2599
|
|
|
2689
|
-
observed_behaviors = []
|
|
2600
|
+
observed_behaviors: list = []
|
|
2690
2601
|
|
|
2691
2602
|
# extract events from selected observations
|
|
2692
2603
|
all_events = [self.pj[cfg.OBSERVATIONS][x][cfg.EVENTS] for x in self.pj[cfg.OBSERVATIONS] if x in selected_observations]
|
|
@@ -2752,8 +2663,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
2752
2663
|
return
|
|
2753
2664
|
|
|
2754
2665
|
# select dir if many observations
|
|
2755
|
-
plot_directory = ""
|
|
2756
|
-
file_format = "png"
|
|
2666
|
+
plot_directory: str = ""
|
|
2667
|
+
file_format: str = "png"
|
|
2757
2668
|
if len(selected_observations) > 1:
|
|
2758
2669
|
plot_directory = QFileDialog.getExistingDirectory(
|
|
2759
2670
|
self,
|
|
@@ -3026,12 +2937,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3026
2937
|
flag_all_upper = True
|
|
3027
2938
|
if pj[cfg.ETHOGRAM]:
|
|
3028
2939
|
for idx in pj[cfg.ETHOGRAM]:
|
|
3029
|
-
if pj[cfg.ETHOGRAM][idx][
|
|
2940
|
+
if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_KEY].islower():
|
|
3030
2941
|
flag_all_upper = False
|
|
3031
2942
|
|
|
3032
2943
|
if pj[cfg.SUBJECTS]:
|
|
3033
2944
|
for idx in pj[cfg.SUBJECTS]:
|
|
3034
|
-
if pj[cfg.SUBJECTS][idx][
|
|
2945
|
+
if pj[cfg.SUBJECTS][idx][cfg.SUBJECT_KEY].islower():
|
|
3035
2946
|
flag_all_upper = False
|
|
3036
2947
|
|
|
3037
2948
|
if (
|
|
@@ -3048,7 +2959,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3048
2959
|
== cfg.YES
|
|
3049
2960
|
):
|
|
3050
2961
|
for idx in pj[cfg.ETHOGRAM]:
|
|
3051
|
-
pj[cfg.ETHOGRAM][idx][
|
|
2962
|
+
pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_KEY] = pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_KEY].lower()
|
|
3052
2963
|
# convert modifier short cuts to lower case
|
|
3053
2964
|
for modifier_set in pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]:
|
|
3054
2965
|
try:
|
|
@@ -3065,7 +2976,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3065
2976
|
logging.warning("error during convertion of modifier short cut to lower case")
|
|
3066
2977
|
|
|
3067
2978
|
for idx in pj[cfg.SUBJECTS]:
|
|
3068
|
-
pj[cfg.SUBJECTS][idx][
|
|
2979
|
+
pj[cfg.SUBJECTS][idx][cfg.SUBJECT_KEY] = pj[cfg.SUBJECTS][idx][cfg.SUBJECT_KEY].lower()
|
|
2980
|
+
|
|
2981
|
+
# check project integrity
|
|
2982
|
+
if self.config_param.get(cfg.CHECK_PROJECT_INTEGRITY, True):
|
|
2983
|
+
msg = project_functions.check_project_integrity(
|
|
2984
|
+
pj,
|
|
2985
|
+
self.timeFormat,
|
|
2986
|
+
project_path,
|
|
2987
|
+
media_file_available=True,
|
|
2988
|
+
)
|
|
2989
|
+
if msg:
|
|
2990
|
+
self.results = dialog.Results_dialog()
|
|
2991
|
+
self.results.setWindowTitle("Project integrity results")
|
|
2992
|
+
self.results.ptText.clear()
|
|
2993
|
+
self.results.ptText.appendHtml(f"Some issues were found in the project<br><br>{msg}")
|
|
2994
|
+
self.results.show()
|
|
3069
2995
|
|
|
3070
2996
|
self.load_project(project_path, project_changed, pj)
|
|
3071
2997
|
del pj
|
|
@@ -3412,19 +3338,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3412
3338
|
|
|
3413
3339
|
del newProjectWindow
|
|
3414
3340
|
|
|
3415
|
-
def save_project_json(self,
|
|
3341
|
+
def save_project_json(self, project_file_name: str) -> int:
|
|
3416
3342
|
"""
|
|
3417
3343
|
save project to JSON file
|
|
3418
3344
|
convert Decimal type in float
|
|
3419
3345
|
|
|
3420
3346
|
Args:
|
|
3421
|
-
|
|
3347
|
+
project_file_name (str): path of project to save
|
|
3422
3348
|
|
|
3423
3349
|
Returns:
|
|
3424
3350
|
str:
|
|
3425
3351
|
"""
|
|
3426
3352
|
|
|
3427
|
-
logging.debug(f"init save_project_json function {
|
|
3353
|
+
logging.debug(f"init save_project_json function {project_file_name}")
|
|
3428
3354
|
|
|
3429
3355
|
if self.save_project_json_started:
|
|
3430
3356
|
logging.warning("Function save_project_json already launched")
|
|
@@ -3447,8 +3373,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3447
3373
|
# project file indentation
|
|
3448
3374
|
file_indentation = self.config_param.get(cfg.PROJECT_FILE_INDENTATION, cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE)
|
|
3449
3375
|
try:
|
|
3450
|
-
if
|
|
3451
|
-
with gzip.open(
|
|
3376
|
+
if project_file_name.endswith(".boris.gz"):
|
|
3377
|
+
with gzip.open(project_file_name, mode="wt", encoding="utf-8") as f_out:
|
|
3452
3378
|
f_out.write(
|
|
3453
3379
|
json.dumps(
|
|
3454
3380
|
self.pj,
|
|
@@ -3457,7 +3383,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3457
3383
|
)
|
|
3458
3384
|
)
|
|
3459
3385
|
else: # .boris and other extensions
|
|
3460
|
-
with open(
|
|
3386
|
+
with open(project_file_name, "w") as f_out:
|
|
3461
3387
|
f_out.write(
|
|
3462
3388
|
json.dumps(
|
|
3463
3389
|
self.pj,
|
|
@@ -3547,11 +3473,36 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3547
3473
|
|
|
3548
3474
|
if self.save_project_json(project_new_file_name) == 0:
|
|
3549
3475
|
self.projectFileName = project_new_file_name
|
|
3476
|
+
|
|
3477
|
+
self.check_project_integrity_open_save()
|
|
3478
|
+
|
|
3550
3479
|
# update windows title
|
|
3551
3480
|
menu_options.update_windows_title(self)
|
|
3552
3481
|
else:
|
|
3553
3482
|
return "Not saved"
|
|
3554
3483
|
|
|
3484
|
+
def check_project_integrity_open_save(self) -> None:
|
|
3485
|
+
"""
|
|
3486
|
+
check project integrity
|
|
3487
|
+
to be used after opening or saving the current project
|
|
3488
|
+
"""
|
|
3489
|
+
if self.automaticBackup:
|
|
3490
|
+
return
|
|
3491
|
+
|
|
3492
|
+
if self.config_param.get(cfg.CHECK_PROJECT_INTEGRITY, True):
|
|
3493
|
+
msg = project_functions.check_project_integrity(
|
|
3494
|
+
self.pj,
|
|
3495
|
+
self.timeFormat,
|
|
3496
|
+
self.projectFileName,
|
|
3497
|
+
media_file_available=True,
|
|
3498
|
+
)
|
|
3499
|
+
if msg:
|
|
3500
|
+
self.results = dialog.Results_dialog()
|
|
3501
|
+
self.results.setWindowTitle("Project integrity results")
|
|
3502
|
+
self.results.ptText.clear()
|
|
3503
|
+
self.results.ptText.appendHtml(f"Some issues were found in the project<br><br>{msg}")
|
|
3504
|
+
self.results.show()
|
|
3505
|
+
|
|
3555
3506
|
def save_project_activated(self):
|
|
3556
3507
|
"""
|
|
3557
3508
|
save current project
|
|
@@ -3617,8 +3568,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3617
3568
|
if r:
|
|
3618
3569
|
self.projectFileName = ""
|
|
3619
3570
|
return r
|
|
3571
|
+
self.check_project_integrity_open_save()
|
|
3572
|
+
|
|
3620
3573
|
else:
|
|
3621
|
-
|
|
3574
|
+
r = self.save_project_json(self.projectFileName)
|
|
3575
|
+
self.check_project_integrity_open_save()
|
|
3576
|
+
return r
|
|
3622
3577
|
|
|
3623
3578
|
return ""
|
|
3624
3579
|
|
|
@@ -4007,7 +3962,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4007
3962
|
"""
|
|
4008
3963
|
returns frame index for player player_idx
|
|
4009
3964
|
"""
|
|
4010
|
-
|
|
3965
|
+
|
|
3966
|
+
if not sys.platform.startswith(cfg.MACOS_CODE):
|
|
3967
|
+
estimated_frame_number = self.dw_player[player_idx].player.estimated_frame_number
|
|
3968
|
+
else:
|
|
3969
|
+
estimated_frame_number = observation_operations.send_command({"command": ["get_property", "estimated_frame_number"]})
|
|
4011
3970
|
if estimated_frame_number is not None:
|
|
4012
3971
|
return estimated_frame_number
|
|
4013
3972
|
else:
|
|
@@ -4340,13 +4299,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4340
4299
|
|
|
4341
4300
|
ct0 = cumulative_time_pos
|
|
4342
4301
|
|
|
4343
|
-
if
|
|
4344
|
-
|
|
4345
|
-
|
|
4302
|
+
if not sys.platform.startswith(cfg.MACOS_CODE):
|
|
4303
|
+
if self.dw_player[0].player.time_pos is not None:
|
|
4304
|
+
for n_player in range(1, len(self.dw_player)):
|
|
4305
|
+
ct = self.getLaps(n_player=n_player)
|
|
4346
4306
|
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4307
|
+
# sync players 2..8 if time diff >= 1 s
|
|
4308
|
+
if (
|
|
4309
|
+
abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
|
|
4310
|
+
>= 1
|
|
4311
|
+
):
|
|
4312
|
+
self.sync_time(n_player, ct0) # self.seek_mediaplayer(ct0, n_player)
|
|
4350
4313
|
|
|
4351
4314
|
currentTimeOffset = dec(cumulative_time_pos + self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TIME_OFFSET])
|
|
4352
4315
|
|
|
@@ -4743,9 +4706,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4743
4706
|
|
|
4744
4707
|
if self.playerType == cfg.MEDIA:
|
|
4745
4708
|
# cumulative time
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4709
|
+
if not sys.platform.startswith(cfg.MACOS_CODE):
|
|
4710
|
+
mem_laps = sum(self.dw_player[n_player].media_durations[0 : self.dw_player[n_player].player.playlist_pos]) + (
|
|
4711
|
+
0 if self.dw_player[n_player].player.time_pos is None else self.dw_player[n_player].player.time_pos * 1000
|
|
4712
|
+
)
|
|
4713
|
+
else:
|
|
4714
|
+
time_pos = observation_operations.send_command({"command": ["get_property", "time-pos"]})
|
|
4715
|
+
# TODO: fix!
|
|
4716
|
+
return dec(time_pos)
|
|
4749
4717
|
|
|
4750
4718
|
return dec(str(round(mem_laps / 1000, 3)))
|
|
4751
4719
|
|
|
@@ -5780,9 +5748,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5780
5748
|
)
|
|
5781
5749
|
return
|
|
5782
5750
|
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
print(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
|
|
5751
|
+
logging.debug(f"{self.config_param.get(cfg.ANALYSIS_PLUGINS, {})=}")
|
|
5786
5752
|
|
|
5787
5753
|
plugin_name = self.sender().text()
|
|
5788
5754
|
if plugin_name not in self.config_param.get(cfg.ANALYSIS_PLUGINS, {}):
|
|
@@ -5790,7 +5756,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5790
5756
|
return
|
|
5791
5757
|
|
|
5792
5758
|
plugin_path = self.config_param.get(cfg.ANALYSIS_PLUGINS, {})[plugin_name]
|
|
5793
|
-
|
|
5759
|
+
|
|
5760
|
+
logging.debug(f"{plugin_path=}")
|
|
5761
|
+
|
|
5794
5762
|
if not pl.Path(plugin_path).is_file():
|
|
5795
5763
|
QMessageBox.critical(self, cfg.programName, f"The plugin {plugin_path} was not found.")
|
|
5796
5764
|
return
|
|
@@ -5801,56 +5769,45 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5801
5769
|
|
|
5802
5770
|
spec = importlib.util.spec_from_file_location(module_name, plugin_path)
|
|
5803
5771
|
plugin_module = importlib.util.module_from_spec(spec)
|
|
5804
|
-
|
|
5772
|
+
|
|
5773
|
+
logging.debug(f"{plugin_module=}")
|
|
5774
|
+
|
|
5805
5775
|
spec.loader.exec_module(plugin_module)
|
|
5806
5776
|
|
|
5807
|
-
|
|
5777
|
+
logging.info(
|
|
5808
5778
|
f"{plugin_module.__plugin_name__} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}"
|
|
5809
5779
|
)
|
|
5810
5780
|
|
|
5811
|
-
"""
|
|
5812
|
-
plugins_dir = pl.Path(__file__).parent / "analysis_plugins"
|
|
5813
|
-
|
|
5814
|
-
print(f"{plugins_dir=}")
|
|
5815
|
-
|
|
5816
|
-
module_path = f"{plugins_dir.name}.{plugin}"
|
|
5817
|
-
|
|
5818
|
-
print(f"{module_path=}")
|
|
5819
|
-
|
|
5820
|
-
try:
|
|
5821
|
-
plugin_module = importlib.import_module(module_path)
|
|
5822
|
-
print(f"{plugin} loaded v.{getattr(plugin_module, '__version__')} v. {getattr(plugin_module, '__version_date__')}")
|
|
5823
|
-
except Exception:
|
|
5824
|
-
QMessageBox.critical(self, cfg.programName, f"Error loding the plugin {plugin}")
|
|
5825
|
-
return
|
|
5826
|
-
|
|
5827
|
-
print(f"{plugin_module=}")
|
|
5828
|
-
"""
|
|
5829
|
-
|
|
5830
5781
|
selected_observations, parameters = self.obs_param()
|
|
5831
5782
|
if not selected_observations:
|
|
5832
5783
|
return
|
|
5833
5784
|
|
|
5834
5785
|
df = project_functions.project2dataframe(self.pj, selected_observations)
|
|
5835
5786
|
|
|
5836
|
-
|
|
5787
|
+
logging.debug("dataframe info")
|
|
5788
|
+
logging.debug(f"{df.info()}")
|
|
5789
|
+
logging.debug(f"{df.head()}")
|
|
5837
5790
|
|
|
5838
|
-
df_results = plugin_module.main(df, observations_list=selected_observations, parameters=parameters)
|
|
5839
|
-
|
|
5840
|
-
from . import view_df
|
|
5791
|
+
df_results, str_results = plugin_module.main(df, observations_list=selected_observations, parameters=parameters)
|
|
5841
5792
|
|
|
5842
5793
|
self.view_dataframe = view_df.View_df(
|
|
5843
5794
|
self.sender().text(), f"{plugin_module.__version__} ({plugin_module.__version_date__})", df_results
|
|
5844
5795
|
)
|
|
5845
5796
|
self.view_dataframe.show()
|
|
5846
5797
|
|
|
5847
|
-
|
|
5798
|
+
if str_results:
|
|
5799
|
+
self.results = dialog.Results_dialog()
|
|
5800
|
+
self.results.setWindowTitle(self.sender().text())
|
|
5801
|
+
self.results.ptText.clear()
|
|
5802
|
+
self.results.ptText.appendPlainText(str_results)
|
|
5803
|
+
self.results.show()
|
|
5848
5804
|
|
|
5849
5805
|
|
|
5850
5806
|
def main():
|
|
5851
5807
|
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
5852
5808
|
|
|
5853
5809
|
app = QApplication(sys.argv)
|
|
5810
|
+
app.setStyle("Fusion")
|
|
5854
5811
|
|
|
5855
5812
|
locale.setlocale(locale.LC_NUMERIC, "C")
|
|
5856
5813
|
|
|
@@ -5884,9 +5841,6 @@ def main():
|
|
|
5884
5841
|
|
|
5885
5842
|
window = MainWindow(ffmpeg_bin)
|
|
5886
5843
|
|
|
5887
|
-
# if window.config_param.get(cfg.DARK_MODE, cfg.DARK_MODE_DEFAULT_VALUE):
|
|
5888
|
-
# app.setStyleSheet(qdarkstyle.load_stylesheet(qt_api="PySide6"))
|
|
5889
|
-
|
|
5890
5844
|
# open project/start observation on command line
|
|
5891
5845
|
|
|
5892
5846
|
project_to_open: str = ""
|
|
@@ -5916,6 +5870,20 @@ def main():
|
|
|
5916
5870
|
QMessageBox.information(window, cfg.programName, msg)
|
|
5917
5871
|
window.load_project(project_path, project_changed, pj)
|
|
5918
5872
|
|
|
5873
|
+
# check project integrity
|
|
5874
|
+
msg = project_functions.check_project_integrity(
|
|
5875
|
+
pj,
|
|
5876
|
+
"S",
|
|
5877
|
+
project_path,
|
|
5878
|
+
media_file_available=True,
|
|
5879
|
+
)
|
|
5880
|
+
if msg:
|
|
5881
|
+
results = dialog.Results_dialog()
|
|
5882
|
+
results.setWindowTitle("Project integrity results")
|
|
5883
|
+
results.ptText.clear()
|
|
5884
|
+
results.ptText.appendHtml(f"Some issues were found in the project<br><br>{msg}")
|
|
5885
|
+
results.show()
|
|
5886
|
+
|
|
5919
5887
|
window.show()
|
|
5920
5888
|
window.raise_()
|
|
5921
5889
|
|