boris-behav-obs 9.6.2__py2.py3-none-any.whl → 9.6.4__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/export_events.py CHANGED
@@ -702,7 +702,7 @@ def export_events_as_textgrid(self) -> None:
702
702
  if not export_dir:
703
703
  return
704
704
 
705
- mem_command = ""
705
+ mem_command: str = ""
706
706
 
707
707
  # see https://www.fon.hum.uva.nl/praat/manual/TextGrid_file_formats.html
708
708
 
@@ -795,6 +795,7 @@ def export_events_as_textgrid(self) -> None:
795
795
  max_time = float(obs_interval[1]) + offset if obs_interval[1] != 0 else float(max_media_duration)
796
796
 
797
797
  # delete events outside time interval
798
+
798
799
  cursor.execute(
799
800
  "DELETE FROM aggregated_events WHERE observation = ? AND (start < ? AND stop < ?) OR (start > ? AND stop > ?)",
800
801
  (
@@ -838,38 +839,30 @@ def export_events_as_textgrid(self) -> None:
838
839
 
839
840
  next_obs: bool = False
840
841
 
841
- """
842
- total_media_duration = round(
843
- observation_operations.observation_total_length(self.pj[cfg.OBSERVATIONS][obs_id]), 3
844
- )
845
- """
846
-
842
+ # number of items for size parameter
847
843
  cursor.execute(
848
844
  (
849
- "SELECT COUNT(DISTINCT subject) FROM aggregated_events "
850
- "WHERE observation = ? AND subject IN ({}) AND type = 'STATE' ".format(
851
- ",".join(["?"] * len(parameters[cfg.SELECTED_SUBJECTS]))
852
- )
845
+ "SELECT COUNT(*) FROM (SELECT * FROM aggregated_events "
846
+ f"WHERE observation = ? AND subject IN ({','.join(['?'] * len(parameters[cfg.SELECTED_SUBJECTS]))}) GROUP BY subject, behavior) "
853
847
  ),
854
848
  [obs_id] + parameters[cfg.SELECTED_SUBJECTS],
855
849
  )
856
850
 
857
- subjectsNum = int(cursor.fetchone()[0])
858
- """subjectsMin = min_time"""
859
- subjectsMax = max_time
851
+ subjects_num = int(cursor.fetchone()[0])
852
+ subjects_max = max_time
860
853
 
861
854
  out = (
862
855
  'File type = "ooTextFile"\n'
863
856
  'Object class = "TextGrid"\n'
864
857
  "\n"
865
858
  f"xmin = 0.0\n"
866
- f"xmax = {subjectsMax}\n"
859
+ f"xmax = {subjects_max}\n"
867
860
  "tiers? <exists>\n"
868
- f"size = {subjectsNum}\n"
861
+ f"size = {subjects_num}\n"
869
862
  "item []:\n"
870
863
  )
871
864
 
872
- subject_index = 0
865
+ subject_index: int = 0
873
866
  for subject in parameters[cfg.SELECTED_SUBJECTS]:
874
867
  if subject not in [
875
868
  x[cfg.EVENT_SUBJECT_FIELD_IDX] if x[cfg.EVENT_SUBJECT_FIELD_IDX] else cfg.NO_FOCAL_SUBJECT
@@ -877,7 +870,8 @@ def export_events_as_textgrid(self) -> None:
877
870
  ]:
878
871
  continue
879
872
 
880
- intervalsMin, intervalsMax = min_time, max_time
873
+ intervalsMin = min_time
874
+ intervalsMax = max_time
881
875
 
882
876
  # STATE events
883
877
  cursor.execute(
@@ -892,68 +886,66 @@ def export_events_as_textgrid(self) -> None:
892
886
  {"start": util.float2decimal(r["start"]), "stop": util.float2decimal(r["stop"]), "code": r["behavior"]}
893
887
  for r in cursor.fetchall()
894
888
  ]
895
- if not rows:
896
- continue
889
+ if rows:
890
+ out += interval_subject_header
897
891
 
898
- out += interval_subject_header
892
+ count = 0
899
893
 
900
- count = 0
901
-
902
- # check if 1st behavior starts at the beginning
903
- if rows[0]["start"] > 0:
904
- count += 1
905
- out += interval_template.format(count=count, name="null", xmin=0.0, xmax=rows[0]["start"])
906
-
907
- for idx, row in enumerate(rows):
908
- # check if events are overlapping
909
- if (idx + 1 < len(rows)) and (row["stop"] > rows[idx + 1]["start"]):
910
- self.results.ptText.appendHtml(
911
- (
912
- f"The events overlap for subject <b>{subject}</b> in the observation <b>{obs_id}</b>. "
913
- "It is not possible to create the Praat TextGrid file."
894
+ # check if 1st behavior starts at the beginning
895
+ if rows[0]["start"] > 0:
896
+ count += 1
897
+ out += interval_template.format(count=count, name="null", xmin=0.0, xmax=rows[0]["start"])
898
+
899
+ for idx, row in enumerate(rows):
900
+ # check if events are overlapping
901
+ if (idx + 1 < len(rows)) and (row["stop"] > rows[idx + 1]["start"]):
902
+ self.results.ptText.appendHtml(
903
+ (
904
+ f"The events overlap for subject <b>{subject}</b> in the observation <b>{obs_id}</b>. "
905
+ "It is not possible to create the Praat TextGrid file."
906
+ )
914
907
  )
915
- )
916
- QApplication.processEvents()
908
+ QApplication.processEvents()
917
909
 
918
- next_obs = True
919
- break
910
+ next_obs = True
911
+ break
920
912
 
921
- count += 1
913
+ count += 1
922
914
 
923
- if (idx + 1 < len(rows)) and (rows[idx + 1]["start"] - dec("0.001") <= row["stop"] < rows[idx + 1]["start"]):
924
- xmax = rows[idx + 1]["start"]
925
- else:
926
- xmax = row["stop"]
915
+ if (idx + 1 < len(rows)) and (rows[idx + 1]["start"] - dec("0.001") <= row["stop"] < rows[idx + 1]["start"]):
916
+ xmax = rows[idx + 1]["start"]
917
+ else:
918
+ xmax = row["stop"]
919
+
920
+ out += interval_template.format(count=count, name=row["code"], xmin=row["start"], xmax=xmax)
921
+
922
+ # check if no behavior
923
+ if (idx + 1 < len(rows)) and (row["stop"] < rows[idx + 1]["start"] - dec("0.001")):
924
+ count += 1
925
+ out += interval_template.format(
926
+ count=count,
927
+ name="null",
928
+ xmin=row["stop"],
929
+ xmax=rows[idx + 1]["start"],
930
+ )
927
931
 
928
- out += interval_template.format(count=count, name=row["code"], xmin=row["start"], xmax=xmax)
932
+ if next_obs:
933
+ break
929
934
 
930
- # check if no behavior
931
- if (idx + 1 < len(rows)) and (row["stop"] < rows[idx + 1]["start"] - dec("0.001")):
935
+ # check if last event ends at the end of media file
936
+ if rows[-1]["stop"] < max_time:
932
937
  count += 1
933
- out += interval_template.format(
934
- count=count,
935
- name="null",
936
- xmin=row["stop"],
937
- xmax=rows[idx + 1]["start"],
938
- )
939
-
940
- if next_obs:
941
- break
942
-
943
- # check if last event ends at the end of media file
944
- if rows[-1]["stop"] < max_time:
945
- count += 1
946
- out += interval_template.format(count=count, name="null", xmin=rows[-1]["stop"], xmax=max_time)
947
-
948
- # add info
949
- subject_index += 1
950
- out = out.format(
951
- subject_index=subject_index,
952
- subject=subject,
953
- intervalsSize=count,
954
- intervalsMin=intervalsMin,
955
- intervalsMax=intervalsMax,
956
- )
938
+ out += interval_template.format(count=count, name="null", xmin=rows[-1]["stop"], xmax=max_time)
939
+
940
+ # add info
941
+ subject_index += 1
942
+ out = out.format(
943
+ subject_index=subject_index,
944
+ subject=subject,
945
+ intervalsSize=count,
946
+ intervalsMin=intervalsMin,
947
+ intervalsMax=intervalsMax,
948
+ )
957
949
 
958
950
  # POINT events
959
951
  cursor.execute(
@@ -987,12 +979,12 @@ def export_events_as_textgrid(self) -> None:
987
979
  continue
988
980
 
989
981
  # check if file already exists
990
- if mem_command != cfg.OVERWRITE_ALL and pl.Path(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid").is_file():
982
+ if mem_command != cfg.OVERWRITE_ALL and pl.Path(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid").is_file():
991
983
  if mem_command == cfg.SKIP_ALL:
992
984
  continue
993
985
  mem_command = dialog.MessageDialog(
994
986
  cfg.programName,
995
- f"The file <b>{pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid</b> already exists.",
987
+ f"The file <b>{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid</b> already exists.",
996
988
  [cfg.OVERWRITE, cfg.OVERWRITE_ALL, cfg.SKIP, cfg.SKIP_ALL, cfg.CANCEL],
997
989
  )
998
990
  if mem_command == cfg.CANCEL:
@@ -1001,13 +993,13 @@ def export_events_as_textgrid(self) -> None:
1001
993
  continue
1002
994
 
1003
995
  try:
1004
- with open(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid", "w") as f:
996
+ with open(f"{pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid", "w") as f:
1005
997
  f.write(out)
1006
998
  file_count += 1
1007
- self.results.ptText.appendHtml(f"File {pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid was created.")
999
+ self.results.ptText.appendHtml(f"File {pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid was created.")
1008
1000
  QApplication.processEvents()
1009
1001
  except Exception:
1010
- self.results.ptText.appendHtml(f"The file {pl.Path(export_dir) / util.safeFileName(obs_id)}.textGrid can not be created.")
1002
+ self.results.ptText.appendHtml(f"The file {pl.Path(export_dir) / util.safeFileName(obs_id)}.TextGrid can not be created.")
1011
1003
  QApplication.processEvents()
1012
1004
 
1013
1005
  self.results.ptText.appendHtml(f"Done. {file_count} file(s) were created in {export_dir}.")
boris/gui_utilities.py CHANGED
@@ -66,7 +66,6 @@ def restore_geometry(widget: QWidget, widget_name: str, default_width_height):
66
66
  ini_file_path = pl.Path.home() / pl.Path(".boris")
67
67
  if ini_file_path.is_file():
68
68
  settings = QSettings(str(ini_file_path), QSettings.IniFormat)
69
- print(settings.value(f"{widget_name} geometry"))
70
69
  widget.restoreGeometry(settings.value(f"{widget_name} geometry"))
71
70
  logging.debug(f"geometry restored for {widget_name} {settings.value(f'{widget_name} geometry')}")
72
71
  else:
@@ -907,8 +907,6 @@ class ModifiersMapCreatorWindow(QMainWindow):
907
907
  """
908
908
  self.area_list.clear()
909
909
 
910
- print(f"{self.polygonsList2=}")
911
-
912
910
  for modifier_name in sorted(self.polygonsList2.keys()):
913
911
  self.area_list.addItem(modifier_name)
914
912
 
@@ -1301,10 +1301,8 @@ def init_mpv(self):
1301
1301
 
1302
1302
  logging.debug("function: init_mpv")
1303
1303
 
1304
- """
1305
- print(f"{self.winId()=}")
1306
- print(f"{str(int(self.winId()))=}")
1307
- """
1304
+ # print(f"{self.winId()=}")
1305
+ # print(f"{str(int(self.winId()))=}")
1308
1306
 
1309
1307
  subprocess.Popen(
1310
1308
  [
boris/param_panel.py CHANGED
@@ -96,12 +96,8 @@ class Param_panel(QDialog, Ui_Dialog):
96
96
  # Set start_time and end_time widgets values even if it is not shown with
97
97
  # more than 1 observation as some analyses might use it (eg: advanced event filtering)
98
98
 
99
- print(f"{self.end_interval=}")
100
-
101
99
  end_interval = self.end_interval if self.end_interval != 0 else self.media_duration
102
100
 
103
- print(f"{end_interval=}")
104
-
105
101
  self.start_time.set_time(self.start_interval)
106
102
  self.end_time.set_time(end_interval)
107
103
  self.frm_time_interval.setEnabled(False)
@@ -129,7 +129,8 @@ class Plot_spectrogram_RT(QWidget):
129
129
  try:
130
130
  wav = wave.open(wav_file, "r")
131
131
  frames = wav.readframes(-1)
132
- sound_info = np.fromstring(frames, dtype=np.int16)
132
+ # sound_info = np.fromstring(frames, dtype=np.int16)
133
+ sound_info = np.frombuffer(frames, dtype=np.int16)
133
134
  frame_rate = wav.getframerate()
134
135
  wav.close()
135
136
  return sound_info, frame_rate
boris/plot_waveform_rt.py CHANGED
@@ -109,7 +109,8 @@ class Plot_waveform_RT(QWidget):
109
109
  try:
110
110
  wav = wave.open(wav_file, "r")
111
111
  frames = wav.readframes(-1)
112
- signal = np.fromstring(frames, dtype=np.int16)
112
+ # signal = np.fromstring(frames, dtype=np.int16)
113
+ signal = np.frombuffer(frames, dtype=np.int16)
113
114
  frame_rate = wav.getframerate()
114
115
  wav.close()
115
116
  return signal, frame_rate
boris/plugins.py CHANGED
@@ -105,9 +105,11 @@ def get_r_plugin_description(plugin_path: str) -> str | None:
105
105
 
106
106
  def load_plugins(self):
107
107
  """
108
- load selected plugins in analysis menu
108
+ load selected plugins in config_param
109
109
  """
110
110
 
111
+ logging.debug("Loading plugins")
112
+
111
113
  def msg():
112
114
  QMessageBox.warning(
113
115
  self,
@@ -124,7 +126,25 @@ def load_plugins(self):
124
126
  for file_ in sorted((Path(__file__).parent / "analysis_plugins").glob("*.py")):
125
127
  if file_.name.startswith("_"):
126
128
  continue
127
- plugin_name = get_plugin_name(file_)
129
+
130
+ logging.debug(f"Loading plugin: {Path(file_).stem}")
131
+
132
+ # test module
133
+ module_name = Path(file_).stem
134
+ spec = importlib.util.spec_from_file_location(module_name, file_)
135
+ plugin_module = importlib.util.module_from_spec(spec)
136
+ spec.loader.exec_module(plugin_module)
137
+ attributes_list = dir(plugin_module)
138
+
139
+ if "__plugin_name__" in attributes_list:
140
+ plugin_name = plugin_module.__plugin_name__
141
+ else:
142
+ continue
143
+
144
+ if "run" not in attributes_list:
145
+ continue
146
+
147
+ # plugin_name = get_plugin_name(file_)
128
148
  if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
129
149
  # check if plugin with same name already loaded
130
150
  if plugin_name in self.config_param[cfg.ANALYSIS_PLUGINS]:
@@ -138,7 +158,25 @@ def load_plugins(self):
138
158
  for file_ in sorted(Path(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, "")).glob("*.py")):
139
159
  if file_.name.startswith("_"):
140
160
  continue
141
- plugin_name = get_plugin_name(file_)
161
+
162
+ logging.debug(f"Loading personal plugin: {Path(file_).stem}")
163
+
164
+ # test module
165
+ module_name = Path(file_).stem
166
+ spec = importlib.util.spec_from_file_location(module_name, file_)
167
+ plugin_module = importlib.util.module_from_spec(spec)
168
+ spec.loader.exec_module(plugin_module)
169
+ attributes_list = dir(plugin_module)
170
+
171
+ if "__plugin_name__" in attributes_list:
172
+ plugin_name = plugin_module.__plugin_name__
173
+ else:
174
+ continue
175
+
176
+ if "run" not in attributes_list:
177
+ continue
178
+
179
+ # plugin_name = get_plugin_name(file_)
142
180
  if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
143
181
  # check if plugin with same name already loaded
144
182
  if plugin_name in self.config_param[cfg.ANALYSIS_PLUGINS]:
boris/preferences.py CHANGED
@@ -60,7 +60,7 @@ class Preferences(QDialog, Ui_prefDialog):
60
60
  # Create a monospace QFont
61
61
  monospace_font = QFont("Courier New") # or "Monospace", "Consolas", "Liberation Mono", etc.
62
62
  monospace_font.setStyleHint(QFont.Monospace)
63
- monospace_font.setPointSize(13)
63
+ monospace_font.setPointSize(12)
64
64
  self.pte_plugin_code.setFont(monospace_font)
65
65
 
66
66
  def browse_plugins_dir(self):
@@ -83,7 +83,6 @@ class Preferences(QDialog, Ui_prefDialog):
83
83
  if plugin_name in [self.lv_all_plugins.item(i).text() for i in range(self.lv_all_plugins.count())]:
84
84
  continue
85
85
  item = QListWidgetItem(plugin_name)
86
- # item = QListWidgetItem(file_.stem)
87
86
  item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
88
87
  item.setCheckState(Qt.Checked)
89
88
  item.setData(100, str(file_))
@@ -152,6 +151,7 @@ def preferences(self):
152
151
 
153
152
  plugin_path = item.data(100)
154
153
 
154
+ # Python plugins
155
155
  if Path(plugin_path).suffix == ".py":
156
156
  import importlib
157
157
 
@@ -159,21 +159,35 @@ def preferences(self):
159
159
  spec = importlib.util.spec_from_file_location(module_name, plugin_path)
160
160
  plugin_module = importlib.util.module_from_spec(spec)
161
161
  spec.loader.exec_module(plugin_module)
162
+ attributes_list = dir(plugin_module)
162
163
 
163
164
  out: list = []
164
- out.append(plugin_module.__plugin_name__ + "\n")
165
- out.append(plugin_module.__author__)
166
- out.append(f"{plugin_module.__version__} ({plugin_module.__version_date__})\n")
167
- out.append(plugin_module.run.__doc__.strip())
165
+ out.append((plugin_module.__plugin_name__ + "\n") if "__plugin_name__" in attributes_list else "No plugin name provided")
166
+ out.append(plugin_module.__author__ if "__author__" in attributes_list else "No author provided")
167
+ version_str: str = ""
168
+ if "__version__" in attributes_list:
169
+ version_str += str(plugin_module.__version__)
170
+ if "__version_date__" in attributes_list:
171
+ version_str += " " if version_str else ""
172
+ version_str += f"({plugin_module.__version_date__})"
173
+
174
+ out.append(f"Version: {version_str}\n" if version_str else "No version provided")
175
+
176
+ # out.append(plugin_module.run.__doc__.strip())
177
+ # description
178
+ if "__description__" in attributes_list:
179
+ out.append("Description:\n")
180
+ out.append(plugin_module.__description__ if "__description__" in attributes_list else "No description provided")
168
181
 
169
182
  preferencesWindow.pte_plugin_description.setPlainText("\n".join(out))
170
183
 
184
+ # R plugins
171
185
  if Path(plugin_path).suffix == ".R":
172
186
  plugin_description = plugins.get_r_plugin_description(plugin_path)
173
187
  if plugin_description is not None:
174
188
  preferencesWindow.pte_plugin_description.setPlainText("\n".join(plugin_description.split("\\n")))
175
189
  else:
176
- preferencesWindow.pte_plugin_description.setPlainText("Plugin description not found")
190
+ preferencesWindow.pte_plugin_description.setPlainText("No description provided")
177
191
 
178
192
  # display plugin code
179
193
  try:
@@ -254,6 +268,7 @@ def preferences(self):
254
268
 
255
269
  preferencesWindow.lw_personal_plugins.clear()
256
270
  if self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, ""):
271
+ # Python plugins
257
272
  for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.py"):
258
273
  if file_.name.startswith("_"):
259
274
  continue
@@ -272,6 +287,7 @@ def preferences(self):
272
287
  item.setData(100, str(file_))
273
288
  preferencesWindow.lw_personal_plugins.addItem(item)
274
289
 
290
+ # R plugins
275
291
  for file_ in Path(self.config_param[cfg.PERSONAL_PLUGINS_DIR]).glob("*.R"):
276
292
  plugin_name = plugins.get_r_plugin_name(file_)
277
293
  if plugin_name is None:
@@ -1021,15 +1021,6 @@ def time_budget_analysis(
1021
1021
  continue
1022
1022
 
1023
1023
  if len(rows) % 2: # unpaired events
1024
- """
1025
- print()
1026
- print(f"{subject=}")
1027
- print(f"{behavior=}")
1028
- print()
1029
- for row in rows:
1030
- print(f"{row['observation']=} {row['occurence']=}")
1031
- print()
1032
- """
1033
1024
  out_cat.append(
1034
1025
  {
1035
1026
  "subject": subject,
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.6.2"
24
- __version_date__ = "2025-08-05"
23
+ __version__ = "9.6.4"
24
+ __version_date__ = "2025-08-29"
boris/video_equalizer.py CHANGED
@@ -60,8 +60,6 @@ class Video_equalizer(QDialog, Ui_Equalizer):
60
60
  if n_player not in self.equalizer:
61
61
  return
62
62
 
63
- print(self.equalizer)
64
-
65
63
  self.hs_brightness.setValue(self.equalizer[n_player]["hs_brightness"])
66
64
  self.lb_brightness.setText(str(self.equalizer[n_player]["hs_brightness"]))
67
65
 
boris/view_df.py CHANGED
@@ -49,8 +49,6 @@ class View_df(QWidget, Ui_Form):
49
49
  self.lb_plugin_info.setText(f"{plugin_name} v. {plugin_version}")
50
50
  self.setWindowTitle(f"{plugin_name} v. {plugin_version}")
51
51
 
52
- # print(f"{self.df=}")
53
-
54
52
  self.pb_close.clicked.connect(self.close)
55
53
  self.pb_save.clicked.connect(self.save)
56
54
 
boris/write_event.py CHANGED
@@ -68,8 +68,6 @@ def write_event(self, event: dict, mem_time: dec) -> int:
68
68
  )
69
69
  return 1
70
70
 
71
- print(f"{mem_time=}")
72
-
73
71
  if mem_time < self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.OBSERVATION_TIME_INTERVAL, [0, 0])[0]:
74
72
  _ = dialog.MessageDialog(
75
73
  cfg.programName,
@@ -104,12 +102,6 @@ def write_event(self, event: dict, mem_time: dec) -> int:
104
102
 
105
103
  # add media creation date/time
106
104
 
107
- """
108
- print(f"{media_file_name=}")
109
- print(f"{mem_time=}")
110
- """
111
- print(f"{self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]=}")
112
-
113
105
  mem_time += dec(
114
106
  self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME][media_file_name_posix]
115
107
  )
@@ -413,7 +405,6 @@ def write_event(self, event: dict, mem_time: dec) -> int:
413
405
  r = modifiers_selector.exec_()
414
406
  if r:
415
407
  selected_modifiers = modifiers_selector.get_modifiers()
416
- # print(f"{selected_modifiers=}")
417
408
 
418
409
  behavior_to_stop_modifier_str: str = ""
419
410
  for idx in util.sorted_keys(selected_modifiers):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.6.2
3
+ Version: 9.6.4
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License-Expression: GPL-3.0-only
@@ -18,16 +18,16 @@ Classifier: Topic :: Scientific/Engineering
18
18
  Requires-Python: >=3.12
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.TXT
21
- Requires-Dist: exifread>=3.0.0
22
- Requires-Dist: numpy>=1.26.4
23
- Requires-Dist: matplotlib>=3.3.3
24
- Requires-Dist: pandas>=2.2.2
25
- Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]>=3
26
- Requires-Dist: pyreadr
21
+ Requires-Dist: exifread==3.5.1
22
+ Requires-Dist: numpy==2.3.2
23
+ Requires-Dist: matplotlib==3.10.5
24
+ Requires-Dist: pandas==2.3.2
25
+ Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]==3.8.0
26
+ Requires-Dist: pyreadr==0.5.3
27
27
  Requires-Dist: pyside6==6.9
28
- Requires-Dist: hachoir>=3.3.0
29
- Requires-Dist: scipy>=1.15.3
30
- Requires-Dist: scikit-learn>=1.7.1
28
+ Requires-Dist: hachoir==3.3.0
29
+ Requires-Dist: scipy==1.16.1
30
+ Requires-Dist: scikit-learn==1.7.1
31
31
  Provides-Extra: dev
32
32
  Requires-Dist: ruff; extra == "dev"
33
33
  Requires-Dist: pytest; extra == "dev"
@@ -49,7 +49,10 @@ You can not longer run BORIS natively on MacOS (since v.8). Some alternatives to
49
49
 
50
50
  It provides also some analysis tools like time budget and some plotting functions.
51
51
 
52
- The BORIS paper has more than [![BORIS citations counter](http://penelope.unito.it/friard/boris_scopus_citations.png) citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications.
52
+ <!-- The BO-RIS paper has more than [![BORIS citations counter](https://penelope.unito.it/friard/boris_scopus_citations.png) citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications. -->
53
+
54
+
55
+ The BORIS paper has more than 2332 citations in peer-reviewed scientific publications.
53
56
 
54
57
 
55
58
 
@@ -59,13 +62,17 @@ See the official [BORIS web site](https://www.boris.unito.it).
59
62
  [![Python web site](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org)
60
63
  ![Python versions](https://img.shields.io/pypi/pyversions/boris-behav-obs)
61
64
  ![BORIS license](https://img.shields.io/pypi/l/boris-behav-obs)
65
+ [![PyPI version](https://img.shields.io/pypi/v/boris-behav-obs.svg)](https://pypi.org/project/boris-behav-obs/)
66
+
62
67
  [![Number of downloads](https://static.pepy.tech/personalized-badge/boris-behav-obs?period=total&units=international_system&left_color=black&right_color=orange&left_text=Downloads)](https://pepy.tech/project/boris-behav-obs)
63
68
  ![commit-activity](https://img.shields.io/github/commit-activity/m/olivierfriard/BORIS)
64
- [![PyPI version](https://img.shields.io/pypi/v/boris-behav-obs.svg)](https://pypi.org/project/boris-behav-obs/)
65
- ![BORIS scopus citations badge](http://penelope.unito.it/friard/boris_scopus_citations.svg)
69
+ ![GitHub last commit](https://img.shields.io/github/last-commit/olivierfriard/BORIS)
66
70
 
71
+ ![BORIS scopus citations badge](https://penelope.unito.it/friard/boris_scopus_citations.svg)
67
72
 
68
73
 
74
+ ![GitHub Repo stars](https://img.shields.io/github/stars/olivierfriard/BORIS?style=flat&label=Stars)
75
+ [![Please Star](https://img.shields.io/badge/⭐-Star%20this%20repo-blue?style=flat-square)](https://github.com/olivierfriard/BORIS/stargazers)
69
76
 
70
77
  # Documentation
71
78
 
@@ -82,8 +89,7 @@ Some [video tutorials](https://www.boris.unito.it/video_tutorials/) are availabl
82
89
  # Bug reports and feature requests
83
90
 
84
91
 
85
- To search for bugs, report them or request a feature, please use the github tracker:
86
- https://github.com/olivierfriard/BORIS/issues
92
+ To search for bugs, report them or request a feature, please use the [GitHub issues tracker](https://github.com/olivierfriard/BORIS/issues)
87
93
 
88
94
 
89
95
 
@@ -129,7 +135,7 @@ GNU General Public License for more details.
129
135
 
130
136
  Distributed with a [GPL v.3 license](LICENSE.TXT).
131
137
 
132
- Copyright (C) 2012-2024 Olivier Friard
138
+ Copyright (C) 2012-2025 Olivier Friard
133
139
 
134
140
 
135
141