boris-behav-obs 9.7.1__py3-none-any.whl → 9.7.15__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/ipc_mpv.py CHANGED
@@ -1,8 +1,29 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 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
+ """
22
+
1
23
  import socket
2
24
  import json
3
25
  import subprocess
4
26
 
5
- # from PySide6.QtCore import QTimer
6
27
  import logging
7
28
  import config as cfg
8
29
 
@@ -36,11 +57,13 @@ class IPC_MPV:
36
57
  self.process = subprocess.Popen(
37
58
  [
38
59
  "mpv",
60
+ "--ontop",
39
61
  "--no-border",
40
62
  "--osc=no", # no on screen commands
41
63
  "--input-ipc-server=" + self.socket_path,
42
64
  # "--wid=" + str(int(self.winId())), # Embed in the widget
43
- "--idle", # Keeps mpv running with no video
65
+ "--idle=yes", # Keeps mpv running with no video
66
+ "--keep-open=always",
44
67
  "--input-default-bindings=no",
45
68
  "--input-vo-keyboard=no",
46
69
  ],
@@ -48,30 +71,6 @@ class IPC_MPV:
48
71
  stderr=subprocess.PIPE,
49
72
  )
50
73
 
51
- '''
52
- def init_socket(self):
53
- """
54
- Initialize the JSON IPC socket.
55
- """
56
- print("init socket")
57
- self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
58
- QTimer.singleShot(5000, self.connect_socket) # Allow time for mpv to initialize
59
- '''
60
-
61
- '''
62
- def connect_socket(self):
63
- """
64
- Connect to the mpv IPC socket.
65
- """
66
- print("connect socket")
67
- try:
68
- self.sock.connect(self.socket_path)
69
- print("Connected to mpv IPC server.")
70
- except socket.error as e:
71
- print(f"Failed to connect to mpv IPC server: {e}")
72
- print("end of connect_socket fucntion")
73
- '''
74
-
75
74
  def send_command(self, command):
76
75
  """
77
76
  Send a JSON command to the mpv IPC server.
@@ -92,9 +91,7 @@ class IPC_MPV:
92
91
  # Parse the response as JSON
93
92
  response_data = json.loads(response.decode("utf-8"))
94
93
  if response_data["error"] != "success":
95
- print(f"send command: {command}")
96
- print(f"{response_data=}")
97
- print()
94
+ logging.warning(f"send command: {command} response data: {response_data}")
98
95
  # Return the 'data' field which contains the playback position
99
96
  return response_data.get("data")
100
97
  except FileNotFoundError:
@@ -128,7 +125,6 @@ class IPC_MPV:
128
125
 
129
126
  @pause.setter
130
127
  def pause(self, value):
131
- print(f"set pause to {value}")
132
128
  return self.send_command({"command": ["set_property", "pause", value]})
133
129
 
134
130
  @property
@@ -143,6 +139,14 @@ class IPC_MPV:
143
139
  def playlist(self):
144
140
  return self.send_command({"command": ["get_property", "playlist"]})
145
141
 
142
+ def playlist_next(self):
143
+ self.send_command({"command": ["playlist-next"]})
144
+ return
145
+
146
+ def playlist_prev(self):
147
+ self.send_command({"command": ["playlist-prev"]})
148
+ return
149
+
146
150
  @property
147
151
  def playlist_pos(self):
148
152
  return self.send_command({"command": ["get_property", "playlist-pos"]})
@@ -168,7 +172,6 @@ class IPC_MPV:
168
172
  @property
169
173
  def playback_time(self):
170
174
  playback_time_ = self.send_command({"command": ["get_property", "playback-time"]})
171
- print(f"playback_time: {playback_time_}")
172
175
  return playback_time_
173
176
 
174
177
  def frame_step(self):
@@ -292,6 +295,24 @@ class IPC_MPV:
292
295
  def core_idle(self):
293
296
  return self.send_command({"command": ["get_property", "core-idle"]})
294
297
 
298
+ @property
299
+ def video_pan_x(self):
300
+ return self.send_command({"command": ["get_property", "video-pan-x"]})
301
+
302
+ @video_pan_x.setter
303
+ def video_pan_x(self, value):
304
+ self.send_command({"command": ["set_property", "video-pan-x", value]})
305
+ return
306
+
307
+ @property
308
+ def video_pan_y(self):
309
+ return self.send_command({"command": ["get_property", "video-pan-y"]})
310
+
311
+ @video_pan_y.setter
312
+ def video_pan_y(self, value):
313
+ self.send_command({"command": ["set_property", "video-pan-y", value]})
314
+ return
315
+
295
316
  """
296
317
  @property
297
318
  def xxx(self):
@@ -134,7 +134,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
134
134
  self.saveMapAction.setShortcut("Ctrl+S")
135
135
  self.saveMapAction.setStatusTip("Save modifiers map")
136
136
  self.saveMapAction.setEnabled(False)
137
- self.saveMapAction.triggered.connect(self.saveMap_clicked)
137
+ self.saveMapAction.triggered.connect(self.save_map_clicked)
138
138
 
139
139
  self.saveAsMapAction = QAction(QIcon(), "Save modifiers map as", self)
140
140
  self.saveAsMapAction.setStatusTip("Save modifiers map as")
@@ -343,7 +343,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
343
343
  )
344
344
 
345
345
  if response == cfg.SAVE:
346
- if not self.saveMap_clicked():
346
+ if not self.save_map_clicked():
347
347
  event.ignore()
348
348
 
349
349
  if response == cfg.CANCEL:
@@ -567,7 +567,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
567
567
  )
568
568
 
569
569
  if response == "Save":
570
- if not self.saveMap_clicked():
570
+ if not self.save_map_clicked():
571
571
  return
572
572
 
573
573
  if response == "Cancel":
@@ -606,7 +606,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
606
606
  )
607
607
 
608
608
  if response == cfg.SAVE:
609
- if not self.saveMap_clicked():
609
+ if not self.save_map_clicked():
610
610
  return
611
611
 
612
612
  if response == cfg.CANCEL:
@@ -733,7 +733,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
733
733
  self.fileName += ".boris_map"
734
734
  self.saveMap()
735
735
 
736
- def saveMap_clicked(self):
736
+ def save_map_clicked(self):
737
737
  if not self.fileName:
738
738
  fn = QFileDialog(self).getSaveFileName(
739
739
  self,
@@ -746,7 +746,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
746
746
  else:
747
747
  self.fileName = fn
748
748
 
749
- if self.fileName and Path(self.fileName).suffix() != ".boris_map":
749
+ if self.fileName and Path(self.fileName).suffix != ".boris_map":
750
750
  self.fileName += ".boris_map"
751
751
 
752
752
  if self.fileName:
boris/observation.py CHANGED
@@ -42,6 +42,7 @@ from PySide6.QtWidgets import (
42
42
  QApplication,
43
43
  QMenu,
44
44
  QListWidgetItem,
45
+ QHeaderView,
45
46
  )
46
47
 
47
48
  from . import config as cfg
@@ -75,7 +76,10 @@ class AssignConverter(QDialog):
75
76
  self.cbb[-1].addItems(["None"] + sorted(converters.keys()))
76
77
 
77
78
  if column_idx in col_conv:
78
- self.cbb[-1].setCurrentIndex((["None"] + sorted(converters.keys())).index(col_conv[column_idx]))
79
+ if col_conv[column_idx] in (["None"] + sorted(converters.keys())):
80
+ self.cbb[-1].setCurrentIndex((["None"] + sorted(converters.keys())).index(col_conv[column_idx]))
81
+ else:
82
+ self.cbb[-1].setCurrentIndex(0)
79
83
  else:
80
84
  self.cbb[-1].setCurrentIndex(0)
81
85
  hbox.addWidget(self.cbb[-1])
@@ -224,6 +228,9 @@ class Observation(QDialog, Ui_Form):
224
228
  self.pbCancel.clicked.connect(self.pbCancel_clicked)
225
229
 
226
230
  self.tw_data_files.cellDoubleClicked[int, int].connect(self.tw_data_files_cellDoubleClicked)
231
+ self.tw_data_files.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
232
+
233
+ self.twVideo1.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
227
234
 
228
235
  self.mediaDurations, self.mediaFPS, self.mediaHasVideo, self.mediaHasAudio, self.media_creation_time = {}, {}, {}, {}, {}
229
236
 
@@ -26,7 +26,7 @@ from decimal import Decimal as dec
26
26
  import json
27
27
  from math import log2, floor
28
28
  import os
29
- import pathlib as pl
29
+ from pathlib import Path
30
30
  import socket
31
31
  import subprocess
32
32
  import sys
@@ -89,10 +89,10 @@ def export_observations_list_clicked(self):
89
89
  return
90
90
 
91
91
  output_format = cfg.FILE_NAME_SUFFIX[filter_]
92
- if pl.Path(file_name).suffix != "." + output_format:
93
- file_name = str(pl.Path(file_name)) + "." + output_format
92
+ if Path(file_name).suffix != "." + output_format:
93
+ file_name = str(Path(file_name)) + "." + output_format
94
94
  # check if file name with extension already exists
95
- if pl.Path(file_name).is_file():
95
+ if Path(file_name).is_file():
96
96
  if dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE]) == cfg.CANCEL:
97
97
  return
98
98
 
@@ -113,6 +113,8 @@ def observations_list(self):
113
113
  result, selected_obs = select_observations.select_observations2(self, cfg.SINGLE)
114
114
 
115
115
  if not selected_obs:
116
+ # activate main window
117
+ self.activateWindow()
116
118
  return
117
119
 
118
120
  if self.observationId:
@@ -122,6 +124,9 @@ def observations_list(self):
122
124
  )
123
125
  if response == cfg.NO:
124
126
  self.show_data_files()
127
+ # activate main window
128
+ self.activateWindow()
129
+
125
130
  return ""
126
131
  else:
127
132
  close_observation(self)
@@ -145,6 +150,8 @@ def observations_list(self):
145
150
  )
146
151
 
147
152
  logging.debug("end observations list")
153
+ # activate main window
154
+ self.activateWindow()
148
155
 
149
156
 
150
157
  def open_observation(self, mode: str) -> str:
@@ -594,7 +601,7 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
594
601
  close_observation(self)
595
602
 
596
603
  observationWindow = observation.Observation(
597
- tmp_dir=self.ffmpeg_cache_dir if (self.ffmpeg_cache_dir and pl.Path(self.ffmpeg_cache_dir).is_dir()) else tempfile.gettempdir(),
604
+ tmp_dir=self.ffmpeg_cache_dir if (self.ffmpeg_cache_dir and Path(self.ffmpeg_cache_dir).is_dir()) else tempfile.gettempdir(),
598
605
  project_path=self.projectFileName,
599
606
  converters=self.pj.get(cfg.CONVERTERS, {}),
600
607
  time_format=self.timeFormat,
@@ -1151,6 +1158,9 @@ def close_observation(self):
1151
1158
  logging.info("Stop plot timer")
1152
1159
  self.plot_timer.stop()
1153
1160
 
1161
+ if self.MPV_IPC_MODE:
1162
+ self.main_window_activation_timer.stop()
1163
+
1154
1164
  for i, player in enumerate(self.dw_player):
1155
1165
  if (
1156
1166
  str(i + 1) in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE]
@@ -1304,7 +1314,7 @@ def check_creation_date(self) -> Tuple[int, dict]:
1304
1314
 
1305
1315
  if ret == 1: # use file creation time
1306
1316
  for media in not_tagged_media_list:
1307
- media_creation_time[media] = pl.Path(media).stat().st_ctime
1317
+ media_creation_time[media] = Path(media).stat().st_ctime
1308
1318
  return (0, media_creation_time) # OK use media file creation date/time
1309
1319
  else:
1310
1320
  return (1, {})
@@ -1340,8 +1350,6 @@ def initialize_new_media_observation(self) -> bool:
1340
1350
  self.playerType = cfg.VIEWER_MEDIA
1341
1351
  return True
1342
1352
 
1343
- # print(f"{self.process=}")
1344
-
1345
1353
  self.playerType = cfg.MEDIA
1346
1354
  self.fps = 0
1347
1355
 
@@ -1365,7 +1373,7 @@ def initialize_new_media_observation(self) -> bool:
1365
1373
 
1366
1374
  # add all media files to media lists
1367
1375
  self.setDockOptions(QMainWindow.AnimatedDocks | QMainWindow.AllowNestedDocks)
1368
- self.dw_player: list = []
1376
+ self.dw_player = []
1369
1377
 
1370
1378
  # check if media creation time used as offset
1371
1379
  # TODO check if cfg.MEDIA_CREATION_TIME dict is present
@@ -1831,8 +1839,8 @@ def initialize_new_media_observation(self) -> bool:
1831
1839
  self.dw_player[i].resize_signal.connect(self.resize_dw)
1832
1840
 
1833
1841
  # add durations list
1834
- self.dw_player[i].media_durations: list = []
1835
- self.dw_player[i].cumul_media_durations: List[int] = [0] # [idx for idx,x in enumerate(l) if l[idx-1]<pos<=x]
1842
+ self.dw_player[i].media_durations = []
1843
+ self.dw_player[i].cumul_media_durations = [0] # [idx for idx,x in enumerate(l) if l[idx-1]<pos<=x]
1836
1844
 
1837
1845
  # add fps list
1838
1846
  self.dw_player[i].fps = {}
@@ -1844,6 +1852,13 @@ def initialize_new_media_observation(self) -> bool:
1844
1852
  if r:
1845
1853
  break
1846
1854
 
1855
+ # start timer for activating the main window
1856
+ self.main_window_activation_timer = QTimer()
1857
+ self.main_window_activation_timer.setInterval(500)
1858
+ # self.main_window_activation_timer.timeout.connect(self.activateWindow)
1859
+ self.main_window_activation_timer.timeout.connect(self.activate_main_window)
1860
+ self.main_window_activation_timer.start()
1861
+
1847
1862
  for mediaFile in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][n_player]:
1848
1863
  logging.debug(f"media file: {mediaFile}")
1849
1864
 
@@ -1887,7 +1902,7 @@ def initialize_new_media_observation(self) -> bool:
1887
1902
  self.dw_player[i].player.playlist_append(media_full_path)
1888
1903
 
1889
1904
  # add media file name to player window title
1890
- self.dw_player[i].setWindowTitle(f"Player #{i + 1} ({pl.Path(media_full_path).name})")
1905
+ self.dw_player[i].setWindowTitle(f"Player #{i + 1} ({Path(media_full_path).name})")
1891
1906
 
1892
1907
  # media duration cumuled in seconds
1893
1908
  self.dw_player[i].cumul_media_durations_sec = [round(dec(x / 1000), 3) for x in self.dw_player[i].cumul_media_durations]
@@ -1964,7 +1979,7 @@ def initialize_new_media_observation(self) -> bool:
1964
1979
 
1965
1980
  menu_options.update_menu(self)
1966
1981
 
1967
- if not self.MPV_IPC_MODE:
1982
+ if self.MPV_IPC_MODE:
1968
1983
  # activate timer
1969
1984
  self.ipc_mpv_timer = QTimer()
1970
1985
  self.ipc_mpv_timer.setInterval(500)
@@ -1996,7 +2011,7 @@ def initialize_new_media_observation(self) -> bool:
1996
2011
  tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
1997
2012
 
1998
2013
  wav_file_path = (
1999
- pl.Path(tmp_dir) / pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
2014
+ Path(tmp_dir) / Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
2000
2015
  )
2001
2016
 
2002
2017
  if not wav_file_path.is_file():
@@ -2012,7 +2027,7 @@ def initialize_new_media_observation(self) -> bool:
2012
2027
  tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
2013
2028
 
2014
2029
  wav_file_path = (
2015
- pl.Path(tmp_dir) / pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
2030
+ Path(tmp_dir) / Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
2016
2031
  )
2017
2032
 
2018
2033
  if not wav_file_path.is_file():
@@ -2296,8 +2311,8 @@ def initialize_new_images_observation(self):
2296
2311
  sorted(
2297
2312
  list(
2298
2313
  set(
2299
- [str(x) for x in pl.Path(full_dir_path).glob(pattern)]
2300
- + [str(x) for x in pl.Path(full_dir_path).glob(pattern.upper())]
2314
+ [str(x) for x in Path(full_dir_path).glob(pattern)]
2315
+ + [str(x) for x in Path(full_dir_path).glob(pattern.upper())]
2301
2316
  )
2302
2317
  )
2303
2318
  )
@@ -2422,26 +2437,31 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
2422
2437
 
2423
2438
  def create_observations(self):
2424
2439
  """
2425
- Create observations from a media file directory
2440
+ Create observations from a directory of media files
2426
2441
  """
2427
- # print(self.pj[cfg.OBSERVATIONS])
2428
2442
 
2429
- dir_path = QFileDialog.getExistingDirectory(None, "Select directory", os.getenv("HOME"))
2430
- if not dir_path:
2443
+ if not (dir_path := QFileDialog.getExistingDirectory(None, "Select directory", os.getenv("HOME"))):
2431
2444
  return
2432
2445
 
2433
- dlg = dialog.Input_dialog(
2434
- label_caption="Set the following observation parameters",
2435
- elements_list=[
2446
+ elements_list: list = []
2447
+ if util.is_subdir(Path(dir_path), Path(self.projectFileName).parent):
2448
+ elements_list.append(("cb", "Use relative paths", False))
2449
+
2450
+ elements_list.extend(
2451
+ [
2436
2452
  ("cb", "Recurse the subdirectories", False),
2437
- ("cb", "Save the absolute media file path", True),
2438
2453
  ("cb", "Visualize spectrogram", False),
2439
2454
  ("cb", "Visualize waveform", False),
2440
2455
  ("cb", "Media creation date as offset", False),
2441
2456
  ("cb", "Close behaviors between videos", False),
2442
2457
  ("dsb", "Time offset (in seconds)", 0.0, 86400, 1, 0, 3),
2443
2458
  ("dsb", "Media scan sampling duration (in seconds)", 0.0, 86400, 1, 0, 3),
2444
- ],
2459
+ ]
2460
+ )
2461
+
2462
+ dlg = dialog.Input_dialog(
2463
+ label_caption="Set the following observation parameters",
2464
+ elements_list=elements_list,
2445
2465
  title="Observation parameters",
2446
2466
  )
2447
2467
  if not dlg.exec_():
@@ -2450,9 +2470,9 @@ def create_observations(self):
2450
2470
  file_count: int = 0
2451
2471
 
2452
2472
  if dlg.elements["Recurse the subdirectories"].isChecked():
2453
- files_list = pl.Path(dir_path).rglob("*")
2473
+ files_list = Path(dir_path).rglob("*")
2454
2474
  else:
2455
- files_list = pl.Path(dir_path).glob("*")
2475
+ files_list = Path(dir_path).glob("*")
2456
2476
 
2457
2477
  for file in files_list:
2458
2478
  if not file.is_file():
@@ -2462,22 +2482,25 @@ def create_observations(self):
2462
2482
  if not r.get("frames_number", 0):
2463
2483
  continue
2464
2484
 
2465
- if dlg.elements["Save the absolute media file path"].isChecked():
2466
- media_file = str(file)
2485
+ if "Use relative paths" in dlg.elements and dlg.elements["Use relative paths"].isChecked():
2486
+ media_file = str(file.relative_to(Path(self.projectFileName).parent))
2467
2487
  else:
2468
- try:
2469
- media_file = str(file.relative_to(pl.Path(self.projectFileName).parent))
2470
- except ValueError:
2471
- QMessageBox.critical(
2472
- self,
2473
- cfg.programName,
2474
- (
2475
- f"the media file <b>{file}</b> can not be relative to the project directory "
2476
- f"(<b>{pl.Path(self.projectFileName).parent}</b>)"
2477
- "<br><br>Aborting the creation of observations"
2478
- ),
2479
- )
2480
- return
2488
+ media_file = str(file)
2489
+
2490
+ # else:
2491
+ # try:
2492
+ # media_file = str(file.relative_to(Path(self.projectFileName).parent))
2493
+ # except ValueError:
2494
+ # QMessageBox.critical(
2495
+ # self,
2496
+ # cfg.programName,
2497
+ # (
2498
+ # f"the media file <b>{file}</b> can not be relative to the project directory "
2499
+ # f"(<b>{Path(self.projectFileName).parent}</b>)"
2500
+ # "<br><br>Aborting the creation of observations"
2501
+ # ),
2502
+ # )
2503
+ # return
2481
2504
 
2482
2505
  if media_file in self.pj[cfg.OBSERVATIONS]:
2483
2506
  QMessageBox.critical(
@@ -2518,4 +2541,6 @@ def create_observations(self):
2518
2541
  else:
2519
2542
  message: str = f"No media file were found in {dir_path}"
2520
2543
 
2544
+ menu_options.update_menu(self)
2545
+
2521
2546
  QMessageBox.information(self, cfg.programName, message)
boris/plot_data_module.py CHANGED
@@ -134,6 +134,8 @@ class Plot_data(QWidget):
134
134
  column_converter=column_converter,
135
135
  )
136
136
 
137
+ print(f"{error_msg=}")
138
+
137
139
  if not result:
138
140
  self.error_msg = error_msg
139
141
  return
@@ -183,7 +183,7 @@ class Plot_spectrogram_RT(QWidget):
183
183
 
184
184
  return {"media_length": self.media_length, "frame_rate": self.frame_rate}
185
185
 
186
- def plot_spectro(self, current_time: float, force_plot: bool = False) -> tuple[float, bool]:
186
+ def plot_spectro(self, current_time: float | None, force_plot: bool = False) -> tuple[float, bool] | None:
187
187
  """
188
188
  plot sound spectrogram centered on the current time
189
189
 
@@ -192,45 +192,54 @@ class Plot_spectrogram_RT(QWidget):
192
192
  force_plot (bool): force plot even if media paused
193
193
  """
194
194
 
195
- if not force_plot and current_time == self.time_mem:
196
- return
195
+ def spectrogram(self, x, window_type, nfft, noverlap, vmin, vmax) -> None:
196
+ window = matplotlib.mlab.window_hanning
197
197
 
198
- self.time_mem = current_time
198
+ if window_type == "hanning":
199
+ window = matplotlib.mlab.window_hanning
199
200
 
200
- self.ax.clear()
201
+ if window_type == "hamming":
202
+ window = signal.get_window(window_type, nfft)
201
203
 
202
- window_type = "blackmanharris" # self.config_param.get(cfg.SPECTROGRAM_WINDOW_TYPE, cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE)
203
- nfft = int(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
204
- noverlap = self.config_param.get(cfg.SPECTROGRAM_NOVERLAP, cfg.SPECTROGRAM_DEFAULT_NOVERLAP)
205
- vmin = self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN)
206
- vmax = self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX)
204
+ if window_type == "blackmanharris":
205
+ window = signal.get_window(window_type, nfft)
207
206
 
208
- # start
209
- if current_time <= self.interval / 2:
207
+ # print(f"{self.frame_rate=} {vmin=} {vmax=} {window_type=} {nfft=} {noverlap=}")
210
208
  self.ax.specgram(
211
- self.sound_info[: int(self.interval * self.frame_rate)],
209
+ x,
212
210
  mode="psd",
213
211
  NFFT=nfft,
214
212
  Fs=self.frame_rate,
215
213
  noverlap=noverlap,
216
- window=signal.get_window(window_type, nfft),
217
- # matplotlib.mlab.window_hanning
218
- # if window_type == "hanning"
219
- # else matplotlib.mlab.window_hamming
220
- # if window_type == "hamming"
221
- # else matplotlib.mlab.window_blackmanharris
222
- # if window_type == "blackmanharris"
223
- # else matplotlib.mlab.window_hanning,
214
+ window=window,
224
215
  cmap=self.spectro_color_map,
225
216
  vmin=vmin,
226
217
  vmax=vmax,
227
- # mode="psd",
228
- ## NFFT=1024,
229
- # Fs=self.frame_rate,
230
- ## noverlap=900,
231
- # cmap=self.spectro_color_map,
232
218
  )
233
219
 
220
+ if not force_plot and current_time == self.time_mem:
221
+ return
222
+
223
+ self.time_mem = current_time
224
+
225
+ self.ax.clear()
226
+
227
+ window_type = self.config_param.get(cfg.SPECTROGRAM_WINDOW_TYPE, cfg.SPECTROGRAM_DEFAULT_WINDOW_TYPE)
228
+ nfft = int(self.config_param.get(cfg.SPECTROGRAM_NFFT, cfg.SPECTROGRAM_DEFAULT_NFFT))
229
+ noverlap = self.config_param.get(cfg.SPECTROGRAM_NOVERLAP, cfg.SPECTROGRAM_DEFAULT_NOVERLAP)
230
+ if self.config_param.get(cfg.SPECTROGRAM_USE_VMIN_VMAX, cfg.SPECTROGRAM_USE_VMIN_VMAX_DEFAULT):
231
+ vmin = self.config_param.get(cfg.SPECTROGRAM_VMIN, cfg.SPECTROGRAM_DEFAULT_VMIN)
232
+ vmax = self.config_param.get(cfg.SPECTROGRAM_VMAX, cfg.SPECTROGRAM_DEFAULT_VMAX)
233
+ else:
234
+ vmin, vmax = None, None
235
+
236
+ if current_time is None:
237
+ return
238
+
239
+ # start
240
+ if current_time <= self.interval / 2:
241
+ spectrogram(self, self.sound_info[: int(self.interval * self.frame_rate)], window_type, nfft, noverlap, vmin, vmax)
242
+
234
243
  self.ax.set_xlim(current_time - self.interval / 2, current_time + self.interval / 2)
235
244
 
236
245
  # cursor
@@ -239,29 +248,7 @@ class Plot_spectrogram_RT(QWidget):
239
248
  elif current_time >= self.media_length - self.interval / 2:
240
249
  i = int(round(len(self.sound_info) - (self.interval * self.frame_rate), 0))
241
250
 
242
- self.ax.specgram(
243
- self.sound_info[i:],
244
- mode="psd",
245
- NFFT=nfft,
246
- Fs=self.frame_rate,
247
- noverlap=noverlap,
248
- window=signal.get_window(window_type, nfft),
249
- # matplotlib.mlab.window_hanning
250
- # if window_type == "hanning"
251
- # else matplotlib.mlab.window_hamming
252
- # if window_type == "hamming"
253
- # else matplotlib.mlab.window_blackmanharris
254
- # if window_type == "blackmanharris"
255
- # else matplotlib.mlab.window_hanning,
256
- cmap=self.spectro_color_map,
257
- vmin=vmin,
258
- vmax=vmax,
259
- # mode="psd",
260
- ## NFFT=1024,
261
- # Fs=self.frame_rate,
262
- ## noverlap=900,
263
- # cmap=self.spectro_color_map,
264
- )
251
+ spectrogram(self, self.sound_info[i:], window_type, nfft, noverlap, vmin, vmax)
265
252
 
266
253
  lim1 = current_time - (self.media_length - self.interval / 2)
267
254
  lim2 = lim1 + self.interval
@@ -276,32 +263,18 @@ class Plot_spectrogram_RT(QWidget):
276
263
 
277
264
  # middle
278
265
  else:
279
- self.ax.specgram(
266
+ spectrogram(
267
+ self,
280
268
  self.sound_info[
281
269
  int(round((current_time - self.interval / 2) * self.frame_rate, 0)) : int(
282
270
  round((current_time + self.interval / 2) * self.frame_rate, 0)
283
271
  )
284
272
  ],
285
- mode="psd",
286
- NFFT=nfft,
287
- Fs=self.frame_rate,
288
- noverlap=noverlap,
289
- window=signal.get_window(window_type, nfft),
290
- # matplotlib.mlab.window_hanning
291
- # if window_type == "hanning"
292
- # else matplotlib.mlab.window_hamming
293
- # if window_type == "hamming"
294
- # else matplotlib.mlab.window_blackmanharris
295
- # if window_type == "blackmanharris"
296
- # else matplotlib.mlab.window_hanning,
297
- cmap=self.spectro_color_map,
298
- vmin=vmin,
299
- vmax=vmax,
300
- # mode="psd",
301
- ## NFFT=1024,
302
- # Fs=self.frame_rate,
303
- ## noverlap=900,
304
- # cmap=self.spectro_color_map,
273
+ window_type,
274
+ nfft,
275
+ noverlap,
276
+ vmin,
277
+ vmax,
305
278
  )
306
279
 
307
280
  self.ax.xaxis.set_major_locator(mticker.FixedLocator(self.ax.get_xticks().tolist()))
@@ -311,6 +284,5 @@ class Plot_spectrogram_RT(QWidget):
311
284
  self.ax.axvline(x=self.interval / 2, color=self.cursor_color, linestyle="-")
312
285
 
313
286
  self.ax.set_ylim(self.sb_freq_min.value(), self.sb_freq_max.value())
314
- """self.figure.subplots_adjust(wspace=0, hspace=0)"""
315
287
 
316
288
  self.canvas.draw()