boris-behav-obs 9.7.1__py3-none-any.whl → 9.7.12__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/core.py CHANGED
@@ -41,9 +41,9 @@ from PIL.ImageQt import Image
41
41
  import subprocess
42
42
  import locale
43
43
  import tempfile
44
+ import math
44
45
  import time
45
46
  import urllib.request
46
- from typing import Union, Tuple
47
47
  from decimal import Decimal as dec
48
48
  from decimal import ROUND_DOWN
49
49
  import gzip
@@ -54,17 +54,7 @@ import shutil
54
54
 
55
55
  matplotlib.use("QtAgg")
56
56
 
57
- from PySide6.QtCore import (
58
- Qt,
59
- QPoint,
60
- Signal,
61
- QEvent,
62
- QDateTime,
63
- QUrl,
64
- QAbstractTableModel,
65
- QElapsedTimer,
66
- QSettings,
67
- )
57
+ from PySide6.QtCore import Qt, QPoint, Signal, QEvent, QDateTime, QUrl, QAbstractTableModel, QElapsedTimer, QSettings, QTimer
68
58
  from PySide6.QtGui import QIcon, QPixmap, QFont, QKeyEvent, QDesktopServices, QColor, QPainter, QPolygon, QAction
69
59
  from PySide6.QtMultimedia import QSoundEffect
70
60
  from PySide6.QtWidgets import (
@@ -242,7 +232,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
242
232
  current_player: int = 0 # id of the selected (left click) video player
243
233
 
244
234
  mem_media_name: str = "" # record current media name. Use to check if media changed
245
- mem_playlist_index: Union[int, None] = None
235
+ mem_playlist_index: int | None = None
246
236
  saved_state = None
247
237
  user_move_slider: bool = False
248
238
  observationId: str = "" # current observation id
@@ -640,7 +630,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
640
630
  """
641
631
  handle click received from coding pad
642
632
  """
643
- q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text=behaviorCode)
633
+ q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text=behaviorCode)
634
+
644
635
  self.keyPressEvent(q)
645
636
 
646
637
  def close_signal_from_coding_pad(self, geometry, preferences):
@@ -654,7 +645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
654
645
  """
655
646
  handle click received from subjects pad
656
647
  """
657
- q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text="#subject#" + subject)
648
+ q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text="#subject#" + subject)
658
649
  self.keyPressEvent(q)
659
650
 
660
651
  def signal_from_subjects_pad(self, event):
@@ -708,7 +699,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
708
699
  self.subjects_pad = subjects_pad.SubjectsPad(self.pj, filtered_subjects)
709
700
  self.subjects_pad.setWindowFlags(Qt.WindowStaysOnTopHint)
710
701
  self.subjects_pad.sendEventSignal.connect(self.signal_from_subjects_pad)
711
- self.subjects_pad.clickSignal.connect(self.click_signal_from_subjects_pad)
702
+ self.subjects_pad.click_signal.connect(self.click_signal_from_subjects_pad)
712
703
  self.subjects_pad.close_signal.connect(self.close_signal_from_subjects_pad)
713
704
  self.subjects_pad.show()
714
705
 
@@ -739,7 +730,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
739
730
  text: str = "Behaviors to show in ethogram list",
740
731
  table: str = cfg.ETHOGRAM,
741
732
  behavior_type: list = cfg.STATE_EVENT_TYPES,
742
- ) -> Tuple[bool, list]:
733
+ ) -> tuple[bool, list]:
743
734
  """
744
735
  allow user to:
745
736
  filter behaviors in ethogram widget
@@ -1510,15 +1501,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1510
1501
  logging.debug("previous media file")
1511
1502
 
1512
1503
  if self.playerType == cfg.MEDIA:
1513
- if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
1514
- return
1504
+ # if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
1505
+ # self.seek_mediaplayer(dec(0))
1506
+ # return
1515
1507
 
1516
1508
  # check if media not first media
1517
1509
  if self.dw_player[0].player.playlist_pos > 0:
1518
1510
  self.dw_player[0].player.playlist_prev()
1519
1511
 
1520
1512
  elif self.dw_player[0].player.playlist_count == 1:
1521
- self.statusbar.showMessage("There is only one media file", 5000)
1513
+ self.seek_mediaplayer(dec(0))
1514
+ # self.statusbar.showMessage("There is only one media file", 5000)
1522
1515
 
1523
1516
  if hasattr(self, "spectro"):
1524
1517
  self.spectro.memChunk = -1
@@ -3682,14 +3675,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3682
3675
  """
3683
3676
  show next frame
3684
3677
  """
3678
+ # frame_step_size = self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE)
3679
+
3685
3680
  if self.playerType == cfg.IMAGES:
3686
3681
  if self.image_idx < len(self.images_list) - 1:
3687
- self.image_idx += 1
3682
+ self.image_idx += 1 # frame_step_size
3688
3683
  self.extract_frame(self.dw_player[0])
3689
3684
 
3690
3685
  if self.playerType == cfg.MEDIA:
3691
3686
  for dw in self.dw_player:
3687
+ # for _ in range(frame_step_size):
3692
3688
  dw.player.frame_step()
3689
+ # time.sleep(0.5)
3693
3690
 
3694
3691
  if self.geometric_measurements_mode:
3695
3692
  self.extract_frame(dw)
@@ -3866,7 +3863,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3866
3863
  write_event.write_event(self, event, cumulative_time)
3867
3864
  # write_event.write_event(self, event, self.getLaps())
3868
3865
 
3869
- def get_frame_index(self, player_idx: int = 0) -> Union[int, str]:
3866
+ def get_frame_index(self, player_idx: int = 0) -> int | str:
3870
3867
  """
3871
3868
  returns frame index for player player_idx
3872
3869
  """
@@ -3947,7 +3944,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3947
3944
  slider_position = self.video_slider.value() / (cfg.SLIDER_MAXIMUM - 1)
3948
3945
  if self.dw_player[0].player.duration is None:
3949
3946
  return
3950
- video_position = slider_position * self.dw_player[0].player.duration
3947
+ print(f"{slider_position=}")
3948
+
3949
+ d = self.dw_player[0].player.duration
3950
+ print(f"{d=}")
3951
+ if d is None:
3952
+ return
3953
+ video_position = slider_position * d
3954
+ # video_position = slider_position * self.dw_player[0].player.duration
3951
3955
  # self.dw_player[0].player.command("seek", str(video_position), "absolute")
3952
3956
  self.dw_player[0].player.seek(video_position, "absolute")
3953
3957
 
@@ -4142,7 +4146,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4142
4146
  self.dw_player[n_player].player.playlist_pos = self.dw_player[n_player].player.playlist_count - 1
4143
4147
  self.seek_mediaplayer(self.dw_player[n_player].media_durations[-1], player=n_player)
4144
4148
 
4145
- def mpv_timer_out(self, value: Union[float, None] = None, scroll_slider=True):
4149
+ def activate_main_window(self):
4150
+ """
4151
+ activate main window in order to capture keyboard events
4152
+ called only in IPC mode
4153
+ """
4154
+ # check if eof reached
4155
+ # print(f"{self.dw_player[0].player.playlist_pos=}")
4156
+ # print(f"{self.dw_player[0].player.playlist_count=}")
4157
+ if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
4158
+ logging.debug("end of playlist reached")
4159
+ if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
4160
+ if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
4161
+ self.pause_video()
4162
+ self.activateWindow()
4163
+
4164
+ def mpv_timer_out(self, value: float | None = None, scroll_slider=True):
4146
4165
  """
4147
4166
  print the media current position and total length for MPV player
4148
4167
  scroll video slider to video position
@@ -4152,13 +4171,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4152
4171
  if not self.observationId:
4153
4172
  return
4154
4173
 
4155
- print("mpv timer out")
4156
-
4157
4174
  cumulative_time_pos = self.getLaps()
4158
- print(f"{cumulative_time_pos=}")
4159
4175
  # get frame index
4160
4176
  frame_idx = self.get_frame_index()
4161
- print(f"{frame_idx=}")
4162
4177
  # frame_idx = 0
4163
4178
 
4164
4179
  if value is None: # ipc mpv
@@ -4205,8 +4220,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4205
4220
  ct = self.getLaps(n_player=n_player)
4206
4221
 
4207
4222
  # sync players 2..8 if time diff >= 1 s
4208
- if abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]))) >= 1:
4209
- self.sync_time(n_player, ct0) # self.seek_mediaplayer(ct0, n_player)
4223
+ if not math.isnan(ct) and not math.isnan(ct0):
4224
+ if (
4225
+ abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
4226
+ >= 1
4227
+ ):
4228
+ self.sync_time(n_player, float(ct0)) # self.seek_mediaplayer(ct0, n_player)
4210
4229
 
4211
4230
  currentTimeOffset = dec(cumulative_time_pos + self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TIME_OFFSET])
4212
4231
 
@@ -4237,12 +4256,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4237
4256
  self.show_current_states_in_subjects_table()
4238
4257
 
4239
4258
  # current media name
4240
- print(f"{self.dw_player[0].player.playlist_pos=}")
4241
- print(f"{self.dw_player[0].player.playlist=}")
4242
-
4243
- if self.dw_player[0].player.playlist_pos is not None:
4244
- current_media_name = Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]).name
4245
- current_playlist_index = self.dw_player[0].player.playlist_pos
4259
+ playlist = self.dw_player[0].player.playlist
4260
+ playlist_pos = self.dw_player[0].player.playlist_pos
4261
+ if playlist is not None and playlist_pos is not None and playlist:
4262
+ current_media_name = Path(playlist[playlist_pos]["filename"]).name
4263
+ current_playlist_index = playlist_pos
4246
4264
  else:
4247
4265
  current_media_name = ""
4248
4266
  current_playlist_index = None
@@ -4315,15 +4333,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4315
4333
 
4316
4334
  self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
4317
4335
 
4318
- print(f"{msg=}")
4319
-
4320
4336
  if msg:
4321
4337
  self.lb_current_media_time.setText(msg)
4322
4338
 
4323
4339
  # set video scroll bar
4324
4340
 
4325
4341
  if scroll_slider and not self.user_move_slider:
4326
- self.video_slider.setValue(round(current_media_time_pos / current_media_duration * (cfg.SLIDER_MAXIMUM - 1)))
4342
+ if current_media_time_pos is not None and current_media_duration is not None:
4343
+ self.video_slider.setValue(round(current_media_time_pos / current_media_duration * (cfg.SLIDER_MAXIMUM - 1)))
4327
4344
 
4328
4345
  def mpv_eof_reached(self):
4329
4346
  """
@@ -4488,7 +4505,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4488
4505
  for x in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
4489
4506
  )
4490
4507
 
4491
- def choose_behavior(self, obs_key) -> Union[None, str]:
4508
+ def choose_behavior(self, obs_key) -> None | str:
4492
4509
  """
4493
4510
  fill listwidget with all behaviors coded by key
4494
4511
 
@@ -4520,7 +4537,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4520
4537
  else:
4521
4538
  return None
4522
4539
 
4523
- def choose_subject(self, subject_key) -> Union[None, str]:
4540
+ def choose_subject(self, subject_key) -> None | str:
4524
4541
  """
4525
4542
  fill listwidget with all subjects coded by key
4526
4543
 
@@ -4602,13 +4619,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4602
4619
 
4603
4620
  if self.playerType == cfg.MEDIA:
4604
4621
  # cumulative time
4622
+ if self.dw_player[n_player].player.time_pos is None:
4623
+ return dec("NaN")
4605
4624
  mem_laps = sum(self.dw_player[n_player].media_durations[0 : self.dw_player[n_player].player.playlist_pos]) + (
4606
4625
  0 if self.dw_player[n_player].player.time_pos is None else self.dw_player[n_player].player.time_pos * 1000
4607
4626
  )
4608
4627
 
4609
4628
  return dec(str(round(mem_laps / 1000, 3)))
4610
4629
 
4611
- def get_obs_time(self, n_player: int = 0) -> Tuple[dec, dec | None]:
4630
+ def get_obs_time(self, n_player: int = 0) -> tuple[dec, dec | None]:
4612
4631
  """
4613
4632
  returns time in current media and cumulative time from begining of observation
4614
4633
  do not add time offset
@@ -4621,7 +4640,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4621
4640
  """
4622
4641
 
4623
4642
  if not self.observationId:
4624
- return dec("0")
4643
+ return dec("0"), dec("0")
4625
4644
 
4626
4645
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
4627
4646
  if "finished" in self.pb_live_obs.text():
@@ -4828,7 +4847,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4828
4847
 
4829
4848
  # play / pause with space bar
4830
4849
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
4831
- if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
4850
+ # if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
4851
+ if ek == Qt.Key_Space:
4832
4852
  if self.liveObservationStarted:
4833
4853
  if (
4834
4854
  dialog.MessageDialog(cfg.programName, "Are you sure to stop the current live observation?", [cfg.YES, cfg.NO])
@@ -5457,7 +5477,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5457
5477
  check if first player ended
5458
5478
  """
5459
5479
 
5460
- print("play_video")
5480
+ logging.debug("play_video")
5461
5481
 
5462
5482
  if self.geometric_measurements_mode:
5463
5483
  return
@@ -5465,6 +5485,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5465
5485
  if self.playerType != cfg.MEDIA:
5466
5486
  return
5467
5487
 
5488
+ # check if playlist is ended. If it is ended restart playlist from beginning
5489
+ if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
5490
+ if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
5491
+ if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
5492
+ self.seek_mediaplayer(dec(0))
5493
+
5468
5494
  # check if player 1 is ended
5469
5495
  for i, dw in enumerate(self.dw_player):
5470
5496
  if (
@@ -5475,9 +5501,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5475
5501
 
5476
5502
  self.lb_player_status.clear()
5477
5503
 
5478
- # if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.VISUALIZE_WAVEFORM, False) \
5479
- # or self.pj[cfg.OBSERVATIONS][self.observationId].get(VISUALIZE_SPECTROGRAM, False):
5480
-
5481
5504
  self.statusbar.showMessage("", 0)
5482
5505
 
5483
5506
  if self.ipc_mpv_timer is not None:
@@ -5554,16 +5577,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5554
5577
 
5555
5578
  decrement = self.fast * self.play_rate if self.config_param.get(cfg.ADAPT_FAST_JUMP, cfg.ADAPT_FAST_JUMP_DEFAULT) else self.fast
5556
5579
 
5557
- new_time = (
5558
- sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5559
- + self.dw_player[0].player.playback_time
5560
- - decrement
5561
- )
5580
+ try:
5581
+ new_time = (
5582
+ sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5583
+ + self.dw_player[0].player.playback_time
5584
+ - decrement
5585
+ )
5586
+ except Exception:
5587
+ return
5562
5588
 
5563
5589
  if new_time < decrement:
5564
5590
  new_time = 0
5565
5591
 
5566
- self.seek_mediaplayer(new_time)
5592
+ self.seek_mediaplayer(dec(new_time))
5567
5593
 
5568
5594
  self.update_visualizations()
5569
5595
 
@@ -5577,13 +5603,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5577
5603
 
5578
5604
  logging.info(f"Jump forward for {increment} seconds")
5579
5605
 
5580
- new_time = (
5581
- sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5582
- + self.dw_player[0].player.playback_time
5583
- + increment
5584
- )
5606
+ try:
5607
+ new_time = (
5608
+ sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5609
+ + self.dw_player[0].player.playback_time
5610
+ + increment
5611
+ )
5612
+ except Exception:
5613
+ return
5585
5614
 
5586
- self.seek_mediaplayer(new_time)
5615
+ self.seek_mediaplayer(dec(new_time))
5587
5616
 
5588
5617
  self.update_visualizations()
5589
5618
 
@@ -5839,6 +5868,15 @@ def main():
5839
5868
  )
5840
5869
  sys.exit()
5841
5870
 
5871
+ if sys.platform.startswith("darwin"):
5872
+ QMessageBox.warning(
5873
+ None,
5874
+ cfg.programName,
5875
+ ("This version of BORIS for macOS is still EXPERIMENTAL and should be used at your own risk."),
5876
+ QMessageBox.Ok | QMessageBox.Default,
5877
+ QMessageBox.NoButton,
5878
+ )
5879
+
5842
5880
  window.show()
5843
5881
  window.raise_() # for overlapping widget (?)
5844
5882
 
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