Commit b1968330937a12ef804a4fffed6a3b3a1a8a6c34
Exists in
master
Merge branch 'master' into multimodal_tracking
# Conflicts: # invesalius/gui/task_navigator.py
Showing
4 changed files
with
135 additions
and
91 deletions
Show diff stats
invesalius/gui/dialogs.py
... | ... | @@ -3554,8 +3554,6 @@ class ObjectCalibrationDialog(wx.Dialog): |
3554 | 3554 | def set_fiducial_callback(state): |
3555 | 3555 | if state: |
3556 | 3556 | Publisher.sendMessage('Set object fiducial', fiducial_index=index) |
3557 | - if self.pedal_connection is not None: | |
3558 | - self.pedal_connection.remove_callback('fiducial') | |
3559 | 3557 | |
3560 | 3558 | ctrl.SetValue(False) |
3561 | 3559 | self.object_fiducial_being_set = None |
... | ... | @@ -3564,10 +3562,17 @@ class ObjectCalibrationDialog(wx.Dialog): |
3564 | 3562 | self.object_fiducial_being_set = index |
3565 | 3563 | |
3566 | 3564 | if self.pedal_connection is not None: |
3567 | - self.pedal_connection.add_callback('fiducial', set_fiducial_callback) | |
3565 | + self.pedal_connection.add_callback( | |
3566 | + name='fiducial', | |
3567 | + callback=set_fiducial_callback, | |
3568 | + remove_when_released=True, | |
3569 | + ) | |
3568 | 3570 | else: |
3569 | 3571 | set_fiducial_callback(True) |
3570 | 3572 | |
3573 | + if self.pedal_connection is not None: | |
3574 | + self.pedal_connection.remove_callback(name='fiducial') | |
3575 | + | |
3571 | 3576 | def SetObjectFiducial(self, fiducial_index): |
3572 | 3577 | coord, coord_raw = self.tracker.GetTrackerCoordinates( |
3573 | 3578 | # XXX: Always use static reference mode when getting the coordinates. This is what the | ... | ... |
invesalius/gui/task_navigator.py
... | ... | @@ -219,6 +219,13 @@ class InnerFoldPanel(wx.Panel): |
219 | 219 | leftSpacing=0, rightSpacing=0) |
220 | 220 | self.dbs_item.Hide() |
221 | 221 | |
222 | + # Fold 6 - Sessions | |
223 | + item = fold_panel.AddFoldPanel(_("Sessions"), collapsed=False) | |
224 | + stw = SessionPanel(item) | |
225 | + fold_panel.ApplyCaptionStyle(item, style) | |
226 | + fold_panel.AddFoldPanelWindow(item, stw, spacing= 0, | |
227 | + leftSpacing=0, rightSpacing=0) | |
228 | + | |
222 | 229 | # Check box for camera update in volume rendering during navigation |
223 | 230 | tooltip = wx.ToolTip(_("Update camera in volume")) |
224 | 231 | checkcamera = wx.CheckBox(self, -1, _('Vol. camera')) |
... | ... | @@ -437,7 +444,7 @@ class NeuronavigationPanel(wx.Panel): |
437 | 444 | checkbox_pedal_pressed.Enable(False) |
438 | 445 | checkbox_pedal_pressed.SetToolTip(tooltip) |
439 | 446 | |
440 | - pedal_connection.add_callback('gui', checkbox_pedal_pressed.SetValue) | |
447 | + pedal_connection.add_callback(name='gui', callback=checkbox_pedal_pressed.SetValue) | |
441 | 448 | |
442 | 449 | self.checkbox_pedal_pressed = checkbox_pedal_pressed |
443 | 450 | else: |
... | ... | @@ -705,19 +712,25 @@ class NeuronavigationPanel(wx.Panel): |
705 | 712 | if state: |
706 | 713 | fiducial_name = const.TRACKER_FIDUCIALS[n]['fiducial_name'] |
707 | 714 | Publisher.sendMessage('Set tracker fiducial', fiducial_name=fiducial_name) |
708 | - if self.pedal_connection is not None: | |
709 | - self.pedal_connection.remove_callback('fiducial') | |
710 | 715 | |
711 | 716 | ctrl.SetValue(False) |
712 | 717 | self.tracker_fiducial_being_set = None |
713 | 718 | |
714 | 719 | if ctrl.GetValue(): |
715 | 720 | self.tracker_fiducial_being_set = n |
721 | + | |
716 | 722 | if self.pedal_connection is not None: |
717 | - self.pedal_connection.add_callback('fiducial', set_fiducial_callback) | |
723 | + self.pedal_connection.add_callback( | |
724 | + name='fiducial', | |
725 | + callback=set_fiducial_callback, | |
726 | + remove_when_released=True, | |
727 | + ) | |
718 | 728 | else: |
719 | 729 | set_fiducial_callback(True) |
720 | 730 | |
731 | + if self.pedal_connection is not None: | |
732 | + self.pedal_connection.remove_callback(name='fiducial') | |
733 | + | |
721 | 734 | def OnStopNavigation(self): |
722 | 735 | select_tracker_elem = self.select_tracker_elem |
723 | 736 | choice_ref = self.choice_ref |
... | ... | @@ -1128,8 +1141,8 @@ class MarkersPanel(wx.Panel): |
1128 | 1141 | alpha_robot : float = 0 |
1129 | 1142 | beta_robot: float = 0 |
1130 | 1143 | gamma_robot : float = 0 |
1131 | - is_target : int = 0 # is_target is int instead of boolean to avoid | |
1132 | - # problems with CSV export | |
1144 | + is_target : bool = False | |
1145 | + session_id : int = 1 | |
1133 | 1146 | |
1134 | 1147 | # x, y, z, alpha, beta, gamma can be jointly accessed as coord |
1135 | 1148 | @property |
... | ... | @@ -1177,27 +1190,44 @@ class MarkersPanel(wx.Panel): |
1177 | 1190 | self.x_robot, self.y_robot, self.z_robot, self.alpha_robot, self.beta_robot, self.gamma_robot = new_robot |
1178 | 1191 | |
1179 | 1192 | @classmethod |
1180 | - def get_headers(cls): | |
1181 | - """Return the list of field names (headers) for exporting to csv.""" | |
1193 | + def to_string_headers(cls): | |
1194 | + """Return the string containing tab-separated list of field names (headers).""" | |
1182 | 1195 | res = [field.name for field in dataclasses.fields(cls)] |
1183 | 1196 | res.extend(['x_world', 'y_world', 'z_world', 'alpha_world', 'beta_world', 'gamma_world']) |
1184 | - return res | |
1197 | + return '\t'.join(map(lambda x: '\"%s\"' % x, res)) | |
1185 | 1198 | |
1186 | - def get_values(self): | |
1187 | - """Return the list of values for exporting to csv.""" | |
1188 | - res = [] | |
1189 | - res.extend(dataclasses.astuple(self)) | |
1199 | + def to_string(self): | |
1200 | + """Serialize to excel-friendly tab-separated string""" | |
1201 | + res = '' | |
1202 | + for field in dataclasses.fields(self.__class__): | |
1203 | + if field.type is str: | |
1204 | + res += ('\"%s\"\t' % getattr(self, field.name)) | |
1205 | + else: | |
1206 | + res += ('%s\t' % str(getattr(self, field.name))) | |
1190 | 1207 | |
1191 | 1208 | # Add world coordinates (in addition to the internal ones). |
1192 | 1209 | position_world, orientation_world = imagedata_utils.convert_invesalius_to_world( |
1193 | 1210 | position=[self.x, self.y, self.z], |
1194 | 1211 | orientation=[self.alpha, self.beta, self.gamma], |
1195 | 1212 | ) |
1196 | - res.extend(position_world) | |
1197 | - res.extend(orientation_world) | |
1198 | 1213 | |
1214 | + res += '\t'.join(map(lambda x: 'N/A' if x is None else str(x), (*position_world, *orientation_world))) | |
1199 | 1215 | return res |
1200 | 1216 | |
1217 | + def from_string(self, str): | |
1218 | + """Deserialize from a tab-separated string. If the string is not | |
1219 | + properly formatted, might throw an exception and leave the object | |
1220 | + in an inconsistent state.""" | |
1221 | + for field, str_val in zip(dataclasses.fields(self.__class__), str.split('\t')): | |
1222 | + if field.type is float: | |
1223 | + setattr(self, field.name, float(str_val)) | |
1224 | + if field.type is int: | |
1225 | + setattr(self, field.name, int(str_val)) | |
1226 | + if field.type is str: | |
1227 | + setattr(self, field.name, str_val[1:-1]) # remove the quotation marks | |
1228 | + if field.type is bool: | |
1229 | + setattr(self, field.name, str_val=='True') | |
1230 | + | |
1201 | 1231 | def __init__(self, parent, tracker): |
1202 | 1232 | wx.Panel.__init__(self, parent) |
1203 | 1233 | try: |
... | ... | @@ -1218,18 +1248,11 @@ class MarkersPanel(wx.Panel): |
1218 | 1248 | self.markers = [] |
1219 | 1249 | self.current_head = 0, 0, 0, 0, 0, 0 |
1220 | 1250 | self.current_robot = 0, 0, 0, 0, 0, 0 |
1221 | - self.list_coord = [] | |
1222 | - self.marker_ind = 0 | |
1223 | - self.tgt_flag = self.tgt_index = None | |
1224 | 1251 | self.nav_status = False |
1225 | - self.mchange = None | |
1226 | - self.flag_target = False | |
1227 | 1252 | |
1228 | 1253 | self.marker_colour = const.MARKER_COLOUR |
1229 | 1254 | self.marker_size = const.MARKER_SIZE |
1230 | - | |
1231 | - # Define CSV dialect for saving/loading markers | |
1232 | - csv.register_dialect('markers_dialect', delimiter='\t', quoting=csv.QUOTE_NONNUMERIC) | |
1255 | + self.current_session = 1 | |
1233 | 1256 | |
1234 | 1257 | # Change marker size |
1235 | 1258 | spin_size = wx.SpinCtrl(self, -1, "", size=wx.Size(40, 23)) |
... | ... | @@ -1284,6 +1307,7 @@ class MarkersPanel(wx.Panel): |
1284 | 1307 | self.lc.InsertColumn(3, 'Z') |
1285 | 1308 | self.lc.InsertColumn(4, 'ID') |
1286 | 1309 | self.lc.InsertColumn(5, 'Target') |
1310 | + self.lc.InsertColumn(6, 'Session') | |
1287 | 1311 | |
1288 | 1312 | self.lc.SetColumnWidth(0, 28) |
1289 | 1313 | self.lc.SetColumnWidth(1, 50) |
... | ... | @@ -1291,6 +1315,7 @@ class MarkersPanel(wx.Panel): |
1291 | 1315 | self.lc.SetColumnWidth(3, 50) |
1292 | 1316 | self.lc.SetColumnWidth(4, 60) |
1293 | 1317 | self.lc.SetColumnWidth(5, 60) |
1318 | + self.lc.SetColumnWidth(5, 50) | |
1294 | 1319 | |
1295 | 1320 | self.lc.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnMouseRightDown) |
1296 | 1321 | self.lc.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemBlink) |
... | ... | @@ -1315,11 +1340,8 @@ class MarkersPanel(wx.Panel): |
1315 | 1340 | Publisher.subscribe(self.CreateMarker, 'Create marker') |
1316 | 1341 | Publisher.subscribe(self.UpdateNavigationStatus, 'Navigation status') |
1317 | 1342 | Publisher.subscribe(self.UpdateSeedCoordinates, 'Update tracts') |
1318 | - Publisher.subscribe(self.UpdateMchange, 'Update matrix change') | |
1319 | - Publisher.subscribe(self.UpdateMRef, 'Update ref matrix') | |
1343 | + Publisher.subscribe(self.OnChangeCurrentSession, 'Current session changed') | |
1320 | 1344 | Publisher.subscribe(self.UpdateRobotCoord, 'Update raw coord') |
1321 | - Publisher.subscribe(self.UpdateObjectMarker2Center, 'Update object marker to center') | |
1322 | - Publisher.subscribe(self.OnObjectTarget, 'Coil at target') | |
1323 | 1345 | |
1324 | 1346 | def __find_target_marker(self): |
1325 | 1347 | """Return the index of the marker currently selected as target (there |
... | ... | @@ -1327,17 +1349,17 @@ class MarkersPanel(wx.Panel): |
1327 | 1349 | for i in range(len(self.markers)): |
1328 | 1350 | if self.markers[i].is_target: |
1329 | 1351 | return i |
1330 | - | |
1352 | + | |
1331 | 1353 | return -1 |
1332 | 1354 | |
1333 | 1355 | def __get_selected_items(self): |
1334 | - """ | |
1356 | + """ | |
1335 | 1357 | Returns a (possibly empty) list of the selected items in the list control. |
1336 | 1358 | """ |
1337 | 1359 | selection = [] |
1338 | 1360 | |
1339 | 1361 | next = self.lc.GetFirstSelected() |
1340 | - | |
1362 | + | |
1341 | 1363 | while next != -1: |
1342 | 1364 | selection.append(next) |
1343 | 1365 | next = self.lc.GetNextSelected(next) |
... | ... | @@ -1366,13 +1388,13 @@ class MarkersPanel(wx.Panel): |
1366 | 1388 | |
1367 | 1389 | # Unset the previous target |
1368 | 1390 | if prev_idx != -1: |
1369 | - self.markers[prev_idx].is_target = 0 | |
1391 | + self.markers[prev_idx].is_target = False | |
1370 | 1392 | self.lc.SetItemBackgroundColour(prev_idx, 'white') |
1371 | 1393 | Publisher.sendMessage('Set target transparency', status=False, index=prev_idx) |
1372 | 1394 | self.lc.SetItem(prev_idx, 5, "") |
1373 | 1395 | |
1374 | 1396 | # Set the new target |
1375 | - self.markers[idx].is_target = 1 | |
1397 | + self.markers[idx].is_target = True | |
1376 | 1398 | self.lc.SetItemBackgroundColour(idx, 'RED') |
1377 | 1399 | self.lc.SetItem(idx, 5, _("Yes")) |
1378 | 1400 | |
... | ... | @@ -1400,27 +1422,14 @@ class MarkersPanel(wx.Panel): |
1400 | 1422 | def UpdateSeedCoordinates(self, root=None, affine_vtk=None, coord_offset=(0, 0, 0)): |
1401 | 1423 | self.current_seed = coord_offset |
1402 | 1424 | |
1403 | - def UpdateMRef(self, m_ref): | |
1404 | - self.m_ref = m_ref | |
1405 | - | |
1406 | - def UpdateMchange(self, mchange): | |
1407 | - self.mchange = mchange | |
1408 | - | |
1409 | 1425 | def UpdateRobotCoord(self, coord_raw, markers_flag): |
1410 | 1426 | self.current_head = coord_raw[1] |
1411 | 1427 | self.current_robot = coord_raw[2] |
1412 | 1428 | |
1413 | - def UpdateObjectMarker2Center(self, s0_raw, t_offset): | |
1414 | - self.s0_raw=s0_raw | |
1415 | - self.t_offset=t_offset | |
1416 | - | |
1417 | - def OnObjectTarget(self, state): | |
1418 | - self.flag_target = state | |
1419 | - | |
1420 | 1429 | def OnMouseRightDown(self, evt): |
1421 | 1430 | # TODO: Enable the "Set as target" only when target is created with registered object |
1422 | 1431 | menu_id = wx.Menu() |
1423 | - edit_id = menu_id.Append(0, _('Edit ID')) | |
1432 | + edit_id = menu_id.Append(0, _('Edit label')) | |
1424 | 1433 | menu_id.Bind(wx.EVT_MENU, self.OnMenuEditMarkerLabel, edit_id) |
1425 | 1434 | color_id = menu_id.Append(2, _('Edit color')) |
1426 | 1435 | menu_id.Bind(wx.EVT_MENU, self.OnMenuSetColor, color_id) |
... | ... | @@ -1530,7 +1539,7 @@ class MarkersPanel(wx.Panel): |
1530 | 1539 | |
1531 | 1540 | if not evt: # called through pubsub |
1532 | 1541 | index = [] |
1533 | - | |
1542 | + | |
1534 | 1543 | if label and (label in self.__list_fiducial_labels()): |
1535 | 1544 | for id_n in range(self.lc.GetItemCount()): |
1536 | 1545 | item = self.lc.GetItem(id_n, 4) |
... | ... | @@ -1544,14 +1553,13 @@ class MarkersPanel(wx.Panel): |
1544 | 1553 | if index: |
1545 | 1554 | if self.__find_target_marker() in index: |
1546 | 1555 | Publisher.sendMessage('Disable or enable coil tracker', status=False) |
1556 | + Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False, | |
1557 | + m_change_robot2ref=None) | |
1547 | 1558 | wx.MessageBox(_("Target deleted."), _("InVesalius 3")) |
1548 | 1559 | |
1549 | 1560 | self.__delete_multiple_markers(index) |
1550 | 1561 | else: |
1551 | 1562 | if evt: # Don't show the warning if called through pubsub |
1552 | - #TODO: reset robot target. (target should be the same target as invesalius?) | |
1553 | - Publisher.sendMessage('Robot target matrix', robot_tracker_flag=False, | |
1554 | - m_change_robot2ref=None) | |
1555 | 1563 | wx.MessageBox(_("No data selected."), _("InVesalius 3")) |
1556 | 1564 | |
1557 | 1565 | def OnCreateMarker(self, evt): |
... | ... | @@ -1563,10 +1571,10 @@ class MarkersPanel(wx.Panel): |
1563 | 1571 | file should not contain any fiducials already in the list.""" |
1564 | 1572 | filename = dlg.ShowLoadSaveDialog(message=_(u"Load markers"), |
1565 | 1573 | wildcard=const.WILDCARD_MARKER_FILES) |
1566 | - | |
1574 | + | |
1567 | 1575 | if not filename: |
1568 | 1576 | return |
1569 | - | |
1577 | + | |
1570 | 1578 | try: |
1571 | 1579 | with open(filename, 'r') as file: |
1572 | 1580 | magick_line = file.readline() |
... | ... | @@ -1575,28 +1583,29 @@ class MarkersPanel(wx.Panel): |
1575 | 1583 | if ver != 0: |
1576 | 1584 | wx.MessageBox(_("Unknown version of the markers file."), _("InVesalius 3")) |
1577 | 1585 | return |
1578 | - | |
1579 | - reader = csv.reader(file, dialect='markers_dialect') | |
1580 | - next(reader) # skip the header line | |
1586 | + | |
1587 | + file.readline() # skip the header line | |
1581 | 1588 | |
1582 | 1589 | # Read the data lines and create markers |
1583 | - for line in reader: | |
1584 | - marker = self.Marker(*line[:-6]) # Discard the last 6 fields (the world coordinates) | |
1590 | + for line in file.readlines(): | |
1591 | + marker = self.Marker() | |
1592 | + marker.from_string(line) | |
1585 | 1593 | self.CreateMarker(coord=marker.coord, colour=marker.colour, size=marker.size, |
1586 | - label=marker.label, is_target=0, seed=marker.seed) | |
1594 | + label=marker.label, is_target=False, seed=marker.seed, session_id=marker.session_id) | |
1587 | 1595 | |
1588 | 1596 | if marker.label in self.__list_fiducial_labels(): |
1589 | 1597 | Publisher.sendMessage('Load image fiducials', label=marker.label, coord=marker.coord) |
1590 | 1598 | |
1591 | - # If the new marker has is_target=1 (True), we first create | |
1592 | - # a marker with is_target=0 (False), and then call __set_marker_as_target | |
1599 | + # If the new marker has is_target=True, we first create | |
1600 | + # a marker with is_target=False, and then call __set_marker_as_target | |
1593 | 1601 | if marker.is_target: |
1594 | 1602 | self.__set_marker_as_target(len(self.markers)-1) |
1595 | 1603 | |
1596 | 1604 | except: |
1597 | - wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) | |
1605 | + wx.MessageBox(_("Invalid markers file."), _("InVesalius 3")) | |
1598 | 1606 | |
1599 | 1607 | def OnMarkersVisibility(self, evt, ctrl): |
1608 | + | |
1600 | 1609 | if ctrl.GetValue(): |
1601 | 1610 | Publisher.sendMessage('Hide all markers', indexes=self.lc.GetItemCount()) |
1602 | 1611 | ctrl.SetLabel('Show') |
... | ... | @@ -1624,12 +1633,11 @@ class MarkersPanel(wx.Panel): |
1624 | 1633 | try: |
1625 | 1634 | with open(filename, 'w', newline='') as file: |
1626 | 1635 | file.writelines(['%s%i\n' % (const.MARKER_FILE_MAGICK_STRING, const.CURRENT_MARKER_FILE_VERSION)]) |
1627 | - writer = csv.writer(file, dialect='markers_dialect') | |
1628 | - writer.writerow(self.Marker.get_headers()) | |
1629 | - writer.writerows(marker.get_values() for marker in self.markers) | |
1636 | + file.writelines(['%s\n' % self.Marker.to_string_headers()]) | |
1637 | + file.writelines('%s\n' % marker.to_string() for marker in self.markers) | |
1630 | 1638 | file.close() |
1631 | 1639 | except: |
1632 | - wx.MessageBox(_("Error writing markers file."), _("InVesalius 3")) | |
1640 | + wx.MessageBox(_("Error writing markers file."), _("InVesalius 3")) | |
1633 | 1641 | |
1634 | 1642 | def OnSelectColour(self, evt, ctrl): |
1635 | 1643 | #TODO: Make sure GetValue returns 3 numbers (without alpha) |
... | ... | @@ -1638,7 +1646,10 @@ class MarkersPanel(wx.Panel): |
1638 | 1646 | def OnSelectSize(self, evt, ctrl): |
1639 | 1647 | self.marker_size = ctrl.GetValue() |
1640 | 1648 | |
1641 | - def CreateMarker(self, coord=None, colour=None, size=None, label='*', is_target=0, seed=None, head=None, robot=None): | |
1649 | + def OnChangeCurrentSession(self, new_session_id): | |
1650 | + self.current_session = new_session_id | |
1651 | + | |
1652 | + def CreateMarker(self, coord=None, colour=None, size=None, label='*', is_target=0, seed=None, head=None, robot=None, session_id=None): | |
1642 | 1653 | new_marker = self.Marker() |
1643 | 1654 | new_marker.coord = coord or self.current_coord |
1644 | 1655 | new_marker.colour = colour or self.marker_colour |
... | ... | @@ -1648,6 +1659,7 @@ class MarkersPanel(wx.Panel): |
1648 | 1659 | new_marker.seed = seed or self.current_seed |
1649 | 1660 | new_marker.head = head or self.current_head |
1650 | 1661 | new_marker.robot = robot or self.current_robot |
1662 | + new_marker.session_id = session_id or self.current_session | |
1651 | 1663 | |
1652 | 1664 | # Note that ball_id is zero-based, so we assign it len(self.markers) before the new marker is added |
1653 | 1665 | Publisher.sendMessage('Add marker', ball_id=len(self.markers), |
... | ... | @@ -1662,7 +1674,8 @@ class MarkersPanel(wx.Panel): |
1662 | 1674 | self.lc.SetItem(num_items, 1, str(round(new_marker.x, 2))) |
1663 | 1675 | self.lc.SetItem(num_items, 2, str(round(new_marker.y, 2))) |
1664 | 1676 | self.lc.SetItem(num_items, 3, str(round(new_marker.z, 2))) |
1665 | - self.lc.SetItem(num_items, 4, str(new_marker.label)) | |
1677 | + self.lc.SetItem(num_items, 4, new_marker.label) | |
1678 | + self.lc.SetItem(num_items, 6, str(new_marker.session_id)) | |
1666 | 1679 | self.lc.EnsureVisible(num_items) |
1667 | 1680 | |
1668 | 1681 | class DbsPanel(wx.Panel): |
... | ... | @@ -2116,6 +2129,30 @@ class TractographyPanel(wx.Panel): |
2116 | 2129 | Publisher.sendMessage('Remove tracts') |
2117 | 2130 | |
2118 | 2131 | |
2132 | +class SessionPanel(wx.Panel): | |
2133 | + def __init__(self, parent): | |
2134 | + wx.Panel.__init__(self, parent) | |
2135 | + try: | |
2136 | + default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) | |
2137 | + except AttributeError: | |
2138 | + default_colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR) | |
2139 | + self.SetBackgroundColour(default_colour) | |
2140 | + | |
2141 | + # session count spinner | |
2142 | + self.__spin_session = wx.SpinCtrl(self, -1, "", size=wx.Size(40, 23)) | |
2143 | + self.__spin_session.SetRange(1, 99) | |
2144 | + self.__spin_session.SetValue(1) | |
2145 | + | |
2146 | + self.__spin_session.Bind(wx.EVT_TEXT, self.OnSessionChanged) | |
2147 | + self.__spin_session.Bind(wx.EVT_SPINCTRL, self.OnSessionChanged) | |
2148 | + | |
2149 | + sizer_create = wx.FlexGridSizer(rows=1, cols=1, hgap=5, vgap=5) | |
2150 | + sizer_create.AddMany([(self.__spin_session, 1)]) | |
2151 | + | |
2152 | + def OnSessionChanged(self, evt): | |
2153 | + Publisher.sendMessage('Current session changed', new_session_id=self.__spin_session.GetValue()) | |
2154 | + | |
2155 | + | |
2119 | 2156 | class InputAttributes(object): |
2120 | 2157 | # taken from https://stackoverflow.com/questions/2466191/set-attributes-from-dictionary-in-python |
2121 | 2158 | def __init__(self, *initial_data, **kwargs): | ... | ... |
invesalius/navigation/navigation.py
... | ... | @@ -316,20 +316,21 @@ class Navigation(): |
316 | 316 | # del jobs |
317 | 317 | |
318 | 318 | if self.pedal_connection is not None: |
319 | - self.pedal_connection.add_callback('navigation', self.PedalStateChanged) | |
319 | + self.pedal_connection.add_callback(name='navigation', callback=self.PedalStateChanged) | |
320 | 320 | |
321 | 321 | def StopNavigation(self): |
322 | 322 | self.event.set() |
323 | 323 | |
324 | 324 | if self.pedal_connection is not None: |
325 | - self.pedal_connection.remove_callback('navigation') | |
325 | + self.pedal_connection.remove_callback(name='navigation') | |
326 | 326 | |
327 | 327 | self.coord_queue.clear() |
328 | 328 | self.coord_queue.join() |
329 | 329 | |
330 | - if self.SerialPortEnabled(): | |
330 | + if self.serial_port_connection is not None: | |
331 | 331 | self.serial_port_connection.join() |
332 | 332 | |
333 | + if self.SerialPortEnabled(): | |
333 | 334 | self.serial_port_queue.clear() |
334 | 335 | self.serial_port_queue.join() |
335 | 336 | ... | ... |
invesalius/net/pedal_connection.py
... | ... | @@ -40,7 +40,7 @@ class PedalConnection(Thread, metaclass=Singleton): |
40 | 40 | |
41 | 41 | self._midi_in = None |
42 | 42 | self._active_inputs = None |
43 | - self._callbacks = {} | |
43 | + self._callback_infos = [] | |
44 | 44 | |
45 | 45 | def _midi_to_pedal(self, msg): |
46 | 46 | # TODO: At this stage, interpret all note_on messages as the pedal being pressed, |
... | ... | @@ -48,24 +48,22 @@ class PedalConnection(Thread, metaclass=Singleton): |
48 | 48 | # message types and be more stringent about the messages. |
49 | 49 | # |
50 | 50 | if msg.type == 'note_on': |
51 | - if not self._callbacks: | |
52 | - print("Pedal pressed, no callbacks registered") | |
53 | - else: | |
54 | - Publisher.sendMessage('Pedal state changed', state=True) | |
55 | - | |
56 | - for callback in self._callbacks.values(): | |
57 | - callback(True) | |
51 | + state = True | |
58 | 52 | |
59 | 53 | elif msg.type == 'note_off': |
60 | - if not self._callbacks: | |
61 | - print("Pedal released, no callbacks registered") | |
62 | - else: | |
63 | - Publisher.sendMessage('Pedal state changed', state=False) | |
54 | + state = False | |
64 | 55 | |
65 | - for callback in self._callbacks.values(): | |
66 | - callback(False) | |
67 | 56 | else: |
68 | 57 | print("Unknown message type received from MIDI device") |
58 | + return | |
59 | + | |
60 | + Publisher.sendMessage('Pedal state changed', state=state) | |
61 | + for callback_info in self._callback_infos: | |
62 | + callback = callback_info['callback'] | |
63 | + callback(state) | |
64 | + | |
65 | + if not state: | |
66 | + self._callback_infos = [callback_info for callback_info in self._callback_infos if not callback_info['remove_when_released']] | |
69 | 67 | |
70 | 68 | def _connect_if_disconnected(self): |
71 | 69 | if self._midi_in is None and len(self._midi_inputs) > 0: |
... | ... | @@ -93,12 +91,15 @@ class PedalConnection(Thread, metaclass=Singleton): |
93 | 91 | def is_connected(self): |
94 | 92 | return self._midi_in is not None |
95 | 93 | |
96 | - def add_callback(self, name, callback): | |
97 | - self._callbacks[name] = callback | |
94 | + def add_callback(self, name, callback, remove_when_released=False): | |
95 | + self._callback_infos.append({ | |
96 | + 'name': name, | |
97 | + 'callback': callback, | |
98 | + 'remove_when_released': remove_when_released, | |
99 | + }) | |
98 | 100 | |
99 | 101 | def remove_callback(self, name): |
100 | - if name in self._callbacks: | |
101 | - del self._callbacks[name] | |
102 | + self._callback_infos = [callback_info for callback_info in self._callback_infos if callback_info['name'] != name] | |
102 | 103 | |
103 | 104 | def run(self): |
104 | 105 | self.in_use = True | ... | ... |