Commit 24de58350db20db24f2267d4ac91feaaf9f24341
Committed by
GitHub
1 parent
9d79c88d
Exists in
master
ADD: Remote control via Socket.IO (#282)
* ADD: Remote control via Socket.IO - Add a command line option (--remote-host) to connect to a remote server via Socket.IO. - Add a hook to send all internal events to the remote server using `from_neuronavigation` event. - Add a Socket.IO event listener for `to_neuronavigation` events, send publish those events internally. * FIX: WxPython-UI modifying Socket.IO messages not working * FIX: Allow sending message to neuronavigation with data None * Review comments: Create RemoteControl class under invesalius/net * Review comments: Move invesalius_pubsub dir to invesalius/pubsub * ADD: Python modules needed by Socket.IO to requirements.txt * MOD: Move python-socketio to optional-requirements.txt - Also, change the requirement name from python-socketio to python-socketio[client], causing all dependencies needed by the Socket.IO client (e.g., requests) to be installed - Due to that, remove explicit 'requests' library from the requirements Co-authored-by: Olli-Pekka Kahilakoski <olli-pekka.kahilakoski@aalto.fi>
Showing
54 changed files
with
234 additions
and
52 deletions
Show diff stats
app.py
... | ... | @@ -51,7 +51,7 @@ try: |
51 | 51 | except ImportError: |
52 | 52 | from wx import SplashScreen |
53 | 53 | |
54 | -from pubsub import pub as Publisher | |
54 | +from invesalius.pubsub import pub as Publisher | |
55 | 55 | |
56 | 56 | #import wx.lib.agw.advancedsplash as agw |
57 | 57 | #if sys.platform.startswith('linux'): |
... | ... | @@ -323,6 +323,10 @@ def parse_comand_line(): |
323 | 323 | |
324 | 324 | parser.add_option("--import-folder", action="store", dest="import_folder") |
325 | 325 | |
326 | + parser.add_option("--remote-host", | |
327 | + action="store", | |
328 | + dest="remote_host") | |
329 | + | |
326 | 330 | parser.add_option("-s", "--save", |
327 | 331 | help="Save the project after an import.") |
328 | 332 | |
... | ... | @@ -503,6 +507,12 @@ def main(): |
503 | 507 | """ |
504 | 508 | options, args = parse_comand_line() |
505 | 509 | |
510 | + if options.remote_host is not None: | |
511 | + from invesalius.net.remote_control import RemoteControl | |
512 | + | |
513 | + remote_control = RemoteControl(options.remote_host) | |
514 | + remote_control.connect() | |
515 | + | |
506 | 516 | if options.no_gui: |
507 | 517 | non_gui_startup(options, args) |
508 | 518 | else: | ... | ... |
docs/devel/example_pubsub.py
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 | # More information about this design pattern can be found at: |
4 | 4 | # http://wiki.wxpython.org/ModelViewController |
5 | 5 | # http://wiki.wxpython.org/PubSub |
6 | -from pubsub import pub as Publisher | |
6 | +from invesalius.pubsub import pub as Publisher | |
7 | 7 | |
8 | 8 | # The maintainer of Pubsub module is Oliver Schoenborn. |
9 | 9 | # Since the end of 2006 Pubsub is now maintained separately on SourceForge at: | ... | ... |
docs/devel/example_singleton_pubsub.py
invesalius/control.py
invesalius/data/coordinates.py
invesalius/data/editor.py
invesalius/data/geometry.py
invesalius/data/imagedata_utils.py
invesalius/data/mask.py
... | ... | @@ -33,7 +33,7 @@ from invesalius.data.volume import VolumeMask |
33 | 33 | import numpy as np |
34 | 34 | import vtk |
35 | 35 | from invesalius_cy import floodfill |
36 | -from pubsub import pub as Publisher | |
36 | +from invesalius.pubsub import pub as Publisher | |
37 | 37 | from scipy import ndimage |
38 | 38 | from vtk.util import numpy_support |
39 | 39 | ... | ... |
invesalius/data/measures.py
invesalius/data/polydata_utils.py
invesalius/data/record_coords.py
... | ... | @@ -23,7 +23,7 @@ import time |
23 | 23 | import wx |
24 | 24 | from numpy import array, savetxt, hstack,vstack, asarray |
25 | 25 | import invesalius.gui.dialogs as dlg |
26 | -from pubsub import pub as Publisher | |
26 | +from invesalius.pubsub import pub as Publisher | |
27 | 27 | |
28 | 28 | |
29 | 29 | class Record(threading.Thread): | ... | ... |
invesalius/data/slice_.py
... | ... | @@ -22,7 +22,7 @@ import tempfile |
22 | 22 | import numpy as np |
23 | 23 | import vtk |
24 | 24 | from scipy import ndimage |
25 | -from pubsub import pub as Publisher | |
25 | +from invesalius.pubsub import pub as Publisher | |
26 | 26 | |
27 | 27 | import invesalius.constants as const |
28 | 28 | import invesalius.data.converters as converters | ... | ... |
invesalius/data/styles.py
... | ... | @@ -31,7 +31,7 @@ from scipy import ndimage |
31 | 31 | from imageio import imsave |
32 | 32 | from scipy.ndimage import generate_binary_structure, watershed_ift |
33 | 33 | from skimage.morphology import watershed |
34 | -from pubsub import pub as Publisher | |
34 | +from invesalius.pubsub import pub as Publisher | |
35 | 35 | |
36 | 36 | import invesalius.constants as const |
37 | 37 | import invesalius.data.converters as converters | ... | ... |
invesalius/data/styles_3d.py
invesalius/data/surface.py
invesalius/data/tractography.py
invesalius/data/trigger.py
invesalius/data/viewer_slice.py
... | ... | @@ -31,7 +31,7 @@ from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
31 | 31 | import invesalius.data.styles as styles |
32 | 32 | import wx |
33 | 33 | import sys |
34 | -from pubsub import pub as Publisher | |
34 | +from invesalius.pubsub import pub as Publisher | |
35 | 35 | |
36 | 36 | try: |
37 | 37 | from agw import floatspin as FS | ... | ... |
invesalius/data/viewer_volume.py
... | ... | @@ -29,7 +29,7 @@ from numpy.core.umath_tests import inner1d |
29 | 29 | import wx |
30 | 30 | import vtk |
31 | 31 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
32 | -from pubsub import pub as Publisher | |
32 | +from invesalius.pubsub import pub as Publisher | |
33 | 33 | import random |
34 | 34 | from scipy.spatial import distance |
35 | 35 | |
... | ... | @@ -622,6 +622,7 @@ class Viewer(wx.Panel): |
622 | 622 | |
623 | 623 | self.ren.AddActor(self.staticballs[self.ball_id]) |
624 | 624 | self.ball_id = self.ball_id + 1 |
625 | + | |
625 | 626 | #self.UpdateRender() |
626 | 627 | self.Refresh() |
627 | 628 | ... | ... |
invesalius/data/volume.py
... | ... | @@ -24,7 +24,7 @@ from distutils.version import LooseVersion |
24 | 24 | import numpy |
25 | 25 | import vtk |
26 | 26 | import wx |
27 | -from pubsub import pub as Publisher | |
27 | +from invesalius.pubsub import pub as Publisher | |
28 | 28 | |
29 | 29 | import invesalius.constants as const |
30 | 30 | import invesalius.project as prj | ... | ... |
invesalius/data/vtk_utils.py
invesalius/gui/bitmap_preview_panel.py
... | ... | @@ -5,7 +5,7 @@ import numpy |
5 | 5 | |
6 | 6 | from vtk.util import numpy_support |
7 | 7 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
8 | -from pubsub import pub as Publisher | |
8 | +from invesalius.pubsub import pub as Publisher | |
9 | 9 | |
10 | 10 | import invesalius.constants as const |
11 | 11 | import invesalius.data.vtk_utils as vtku | ... | ... |
invesalius/gui/brain_seg_dialog.py
invesalius/gui/data_notebook.py
... | ... | @@ -34,7 +34,7 @@ except ImportError: |
34 | 34 | import wx.lib.flatnotebook as fnb |
35 | 35 | |
36 | 36 | import wx.lib.platebtn as pbtn |
37 | -from pubsub import pub as Publisher | |
37 | +from invesalius.pubsub import pub as Publisher | |
38 | 38 | |
39 | 39 | import invesalius.constants as const |
40 | 40 | import invesalius.data.slice_ as slice_ | ... | ... |
invesalius/gui/default_tasks.py
... | ... | @@ -22,7 +22,7 @@ try: |
22 | 22 | import wx.lib.agw.foldpanelbar as fpb |
23 | 23 | except ModuleNotFoundError: |
24 | 24 | import wx.lib.foldpanelbar as fpb |
25 | -from pubsub import pub as Publisher | |
25 | +from invesalius.pubsub import pub as Publisher | |
26 | 26 | |
27 | 27 | import invesalius.constants as const |
28 | 28 | import invesalius.gui.data_notebook as nb | ... | ... |
invesalius/gui/default_viewers.py
... | ... | @@ -21,7 +21,7 @@ import os |
21 | 21 | |
22 | 22 | import wx |
23 | 23 | import wx.lib.agw.fourwaysplitter as fws |
24 | -from pubsub import pub as Publisher | |
24 | +from invesalius.pubsub import pub as Publisher | |
25 | 25 | |
26 | 26 | import invesalius.data.viewer_slice as slice_viewer |
27 | 27 | import invesalius.data.viewer_volume as volume_viewer |
... | ... | @@ -316,7 +316,7 @@ class VolumeInteraction(wx.Panel): |
316 | 316 | |
317 | 317 | import wx.lib.platebtn as pbtn |
318 | 318 | import wx.lib.buttons as btn |
319 | -from pubsub import pub as Publisher | |
319 | +from invesalius.pubsub import pub as Publisher | |
320 | 320 | import wx.lib.colourselect as csel |
321 | 321 | |
322 | 322 | RAYCASTING_TOOLS = wx.NewId() | ... | ... |
invesalius/gui/dialogs.py
... | ... | @@ -47,7 +47,7 @@ from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
47 | 47 | from wx.lib import masked |
48 | 48 | from wx.lib.agw import floatspin |
49 | 49 | from wx.lib.wordwrap import wordwrap |
50 | -from pubsub import pub as Publisher | |
50 | +from invesalius.pubsub import pub as Publisher | |
51 | 51 | |
52 | 52 | try: |
53 | 53 | from wx.adv import AboutDialogInfo, AboutBox | ... | ... |
invesalius/gui/dicom_preview_panel.py
... | ... | @@ -29,7 +29,7 @@ import vtk |
29 | 29 | |
30 | 30 | from vtk.util import numpy_support |
31 | 31 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
32 | -from pubsub import pub as Publisher | |
32 | +from invesalius.pubsub import pub as Publisher | |
33 | 33 | |
34 | 34 | import invesalius.constants as const |
35 | 35 | import invesalius.reader.dicom_reader as dicom_reader | ... | ... |
invesalius/gui/frame.py
... | ... | @@ -43,7 +43,7 @@ import wx.lib.popupctl as pc |
43 | 43 | from invesalius import inv_paths |
44 | 44 | from invesalius.gui import project_properties |
45 | 45 | from wx.lib.agw.aui.auibar import AUI_TB_PLAIN_BACKGROUND, AuiToolBar |
46 | -from pubsub import pub as Publisher | |
46 | +from invesalius.pubsub import pub as Publisher | |
47 | 47 | |
48 | 48 | try: |
49 | 49 | from wx.adv import TaskBarIcon as wx_TaskBarIcon | ... | ... |
invesalius/gui/import_bitmap_panel.py
... | ... | @@ -18,7 +18,7 @@ |
18 | 18 | #-------------------------------------------------------------------------- |
19 | 19 | import wx |
20 | 20 | import wx.gizmos as gizmos |
21 | -from pubsub import pub as Publisher | |
21 | +from invesalius.pubsub import pub as Publisher | |
22 | 22 | import wx.lib.splitter as spl |
23 | 23 | |
24 | 24 | import invesalius.constants as const | ... | ... |
invesalius/gui/import_network_panel.py
invesalius/gui/import_panel.py
... | ... | @@ -18,7 +18,7 @@ |
18 | 18 | #-------------------------------------------------------------------------- |
19 | 19 | import wx |
20 | 20 | import wx.gizmos as gizmos |
21 | -from pubsub import pub as Publisher | |
21 | +from invesalius.pubsub import pub as Publisher | |
22 | 22 | import wx.lib.splitter as spl |
23 | 23 | |
24 | 24 | import invesalius.constants as const | ... | ... |
invesalius/gui/preferences.py
... | ... | @@ -4,7 +4,7 @@ import invesalius.constants as const |
4 | 4 | import invesalius.session as ses |
5 | 5 | import wx |
6 | 6 | from invesalius.gui.language_dialog import ComboBoxLanguage |
7 | -from pubsub import pub as Publisher | |
7 | +from invesalius.pubsub import pub as Publisher | |
8 | 8 | |
9 | 9 | |
10 | 10 | class Preferences(wx.Dialog): | ... | ... |
invesalius/gui/project_properties.py
invesalius/gui/task_exporter.py
... | ... | @@ -29,7 +29,7 @@ except ImportError: |
29 | 29 | import wx.lib.hyperlink as hl |
30 | 30 | |
31 | 31 | import wx.lib.platebtn as pbtn |
32 | -from pubsub import pub as Publisher | |
32 | +from invesalius.pubsub import pub as Publisher | |
33 | 33 | |
34 | 34 | import invesalius.constants as const |
35 | 35 | import invesalius.gui.dialogs as dlg | ... | ... |
invesalius/gui/task_importer.py
... | ... | @@ -26,7 +26,7 @@ except ImportError: |
26 | 26 | import wx.lib.hyperlink as hl |
27 | 27 | import wx.lib.platebtn as pbtn |
28 | 28 | |
29 | -from pubsub import pub as Publisher | |
29 | +from invesalius.pubsub import pub as Publisher | |
30 | 30 | |
31 | 31 | import invesalius.constants as const |
32 | 32 | import invesalius.gui.dialogs as dlg | ... | ... |
invesalius/gui/task_navigator.py
... | ... | @@ -42,7 +42,7 @@ except ImportError: |
42 | 42 | |
43 | 43 | import wx.lib.colourselect as csel |
44 | 44 | import wx.lib.masked.numctrl |
45 | -from pubsub import pub as Publisher | |
45 | +from invesalius.pubsub import pub as Publisher | |
46 | 46 | from time import sleep |
47 | 47 | |
48 | 48 | import invesalius.constants as const | ... | ... |
invesalius/gui/task_slice.py
... | ... | @@ -31,7 +31,7 @@ except ImportError: |
31 | 31 | |
32 | 32 | import wx.lib.platebtn as pbtn |
33 | 33 | import wx.lib.colourselect as csel |
34 | -from pubsub import pub as Publisher | |
34 | +from invesalius.pubsub import pub as Publisher | |
35 | 35 | |
36 | 36 | import invesalius.data.mask as mask |
37 | 37 | import invesalius.data.slice_ as slice_ | ... | ... |
invesalius/gui/task_surface.py
... | ... | @@ -28,7 +28,7 @@ except ImportError: |
28 | 28 | import wx.lib.hyperlink as hl |
29 | 29 | import wx.lib.foldpanelbar as fpb |
30 | 30 | |
31 | -from pubsub import pub as Publisher | |
31 | +from invesalius.pubsub import pub as Publisher | |
32 | 32 | import wx.lib.colourselect as csel |
33 | 33 | import wx.lib.scrolledpanel as scrolled |
34 | 34 | ... | ... |
invesalius/gui/task_tools.py
... | ... | @@ -27,7 +27,7 @@ except ImportError: |
27 | 27 | import wx.lib.hyperlink as hl |
28 | 28 | |
29 | 29 | import wx.lib.platebtn as pbtn |
30 | -from pubsub import pub as Publisher | |
30 | +from invesalius.pubsub import pub as Publisher | |
31 | 31 | |
32 | 32 | import invesalius.constants as constants |
33 | 33 | import invesalius.constants as const | ... | ... |
invesalius/gui/widgets/canvas_renderer.py
invesalius/gui/widgets/clut_raycasting.py
invesalius/gui/widgets/slice_menu.py
... | ... | @@ -26,7 +26,7 @@ except(ImportError): |
26 | 26 | from ordereddict import OrderedDict |
27 | 27 | |
28 | 28 | import wx |
29 | -from pubsub import pub as Publisher | |
29 | +from invesalius.pubsub import pub as Publisher | |
30 | 30 | |
31 | 31 | import invesalius.constants as const |
32 | 32 | import invesalius.data.slice_ as sl | ... | ... |
... | ... | @@ -0,0 +1,83 @@ |
1 | +#!/usr/bin/env python3 | |
2 | +#-------------------------------------------------------------------------- | |
3 | +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | |
4 | +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | |
5 | +# Homepage: http://www.softwarepublico.gov.br | |
6 | +# Contact: invesalius@cti.gov.br | |
7 | +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | |
8 | +#-------------------------------------------------------------------------- | |
9 | +# Este programa e software livre; voce pode redistribui-lo e/ou | |
10 | +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | |
11 | +# publicada pela Free Software Foundation; de acordo com a versao 2 | |
12 | +# da Licenca. | |
13 | +# | |
14 | +# Este programa eh distribuido na expectativa de ser util, mas SEM | |
15 | +# QUALQUER GARANTIA; sem mesmo a garantia implicita de | |
16 | +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | |
17 | +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | |
18 | +# detalhes. | |
19 | +#------------------------------------------------------------------------- | |
20 | + | |
21 | +import time | |
22 | + | |
23 | +import socketio | |
24 | +import wx | |
25 | + | |
26 | +from invesalius.pubsub import pub as Publisher | |
27 | + | |
28 | +class RemoteControl: | |
29 | + def __init__(self, remote_host): | |
30 | + self._remote_host = remote_host | |
31 | + self._connected = False | |
32 | + self._sio = None | |
33 | + | |
34 | + def _on_connect(self): | |
35 | + print("Connected to {}".format(self._remote_host)) | |
36 | + self._connected = True | |
37 | + | |
38 | + def _on_disconnect(self): | |
39 | + print("Disconnected") | |
40 | + self._connected = False | |
41 | + | |
42 | + def _to_neuronavigation(self, msg): | |
43 | + topic = msg["topic"] | |
44 | + data = msg["data"] | |
45 | + if data is None: | |
46 | + data = {} | |
47 | + | |
48 | + print("Received an event into topic '{}' with data {}".format(topic, str(data))) | |
49 | + Publisher.sendMessage_no_hook( | |
50 | + topicName=topic, | |
51 | + **data | |
52 | + ) | |
53 | + | |
54 | + def _to_neuronavigation_wrapper(self, msg): | |
55 | + # wx.CallAfter wrapping is needed to make messages that update WxPython UI work properly, as the | |
56 | + # Socket.IO listener runs inside a thread. (See WxPython and thread-safety for more information.) | |
57 | + wx.CallAfter(self._to_neuronavigation, msg) | |
58 | + | |
59 | + def connect(self): | |
60 | + self._sio = socketio.Client() | |
61 | + | |
62 | + self._sio.on('connect', self._on_connect) | |
63 | + self._sio.on('disconnect', self._on_disconnect) | |
64 | + self._sio.on('to_neuronavigation', self._to_neuronavigation_wrapper) | |
65 | + | |
66 | + self._sio.connect(self._remote_host) | |
67 | + | |
68 | + while not self._connected: | |
69 | + print("Connecting...") | |
70 | + time.sleep(1.0) | |
71 | + | |
72 | + def _emit(topic, data): | |
73 | + print("Emitting data {} to topic {}".format(data, topic)) | |
74 | + try: | |
75 | + if isinstance(topic, str): | |
76 | + self._sio.emit("from_neuronavigation", { | |
77 | + "topic": topic, | |
78 | + "data": data, | |
79 | + }) | |
80 | + except TypeError: | |
81 | + pass | |
82 | + | |
83 | + Publisher.add_sendMessage_hook(_emit) | ... | ... |
invesalius/plugins.py
invesalius/presets.py
invesalius/project.py
... | ... | @@ -0,0 +1,86 @@ |
1 | +#-------------------------------------------------------------------------- | |
2 | +# Software: InVesalius - Software de Reconstrucao 3D de Imagens Medicas | |
3 | +# Copyright: (C) 2001 Centro de Pesquisas Renato Archer | |
4 | +# Homepage: http://www.softwarepublico.gov.br | |
5 | +# Contact: invesalius@cti.gov.br | |
6 | +# License: GNU - GPL 2 (LICENSE.txt/LICENCA.txt) | |
7 | +#-------------------------------------------------------------------------- | |
8 | +# Este programa e software livre; voce pode redistribui-lo e/ou | |
9 | +# modifica-lo sob os termos da Licenca Publica Geral GNU, conforme | |
10 | +# publicada pela Free Software Foundation; de acordo com a versao 2 | |
11 | +# da Licenca. | |
12 | +# | |
13 | +# Este programa eh distribuido na expectativa de ser util, mas SEM | |
14 | +# QUALQUER GARANTIA; sem mesmo a garantia implicita de | |
15 | +# COMERCIALIZACAO ou de ADEQUACAO A QUALQUER PROPOSITO EM | |
16 | +# PARTICULAR. Consulte a Licenca Publica Geral GNU para obter mais | |
17 | +# detalhes. | |
18 | +#-------------------------------------------------------------------------- | |
19 | + | |
20 | +from typing import Callable | |
21 | + | |
22 | +from pubsub import pub as Publisher | |
23 | +from pubsub.core.listener import UserListener | |
24 | + | |
25 | +__all__ = [ | |
26 | + # subscribing | |
27 | + 'subscribe', | |
28 | + 'unsubscribe', | |
29 | + | |
30 | + # publishing | |
31 | + 'sendMessage', | |
32 | + 'sendMessage_no_hook', | |
33 | + | |
34 | + # adding hooks | |
35 | + 'add_sendMessage_hook' | |
36 | +] | |
37 | + | |
38 | +Hook = Callable[[str, dict], None] | |
39 | + | |
40 | +sendMessage_hook: Hook = None | |
41 | + | |
42 | +def add_sendMessage_hook(hook: Hook): | |
43 | + """Add a hook for sending messages. The hook is a function that takes the topic | |
44 | + name as the first parameter and the message dict as the second parameter, and | |
45 | + returns None. | |
46 | + | |
47 | + :param hook: | |
48 | + """ | |
49 | + global sendMessage_hook | |
50 | + sendMessage_hook = hook | |
51 | + | |
52 | +def subscribe(listener: UserListener, topicName: str, **curriedArgs): | |
53 | + """Subscribe to a topic. | |
54 | + | |
55 | + :param listener: | |
56 | + :param topicName: | |
57 | + :param curriedArgs: | |
58 | + """ | |
59 | + subscribedListener, success = Publisher.subscribe(listener, topicName, **curriedArgs) | |
60 | + return subscribedListener, success | |
61 | + | |
62 | +def unsubscribe(*args, **kwargs): | |
63 | + """Unsubscribe from a topic. | |
64 | + | |
65 | + """ | |
66 | + Publisher.unsubscribe(*args, **kwargs) | |
67 | + | |
68 | +def sendMessage(topicName: str, **msgdata): | |
69 | + """Send a message in a given topic. | |
70 | + | |
71 | + :param topicName: | |
72 | + :param msgdata: | |
73 | + """ | |
74 | + Publisher.sendMessage(topicName, **msgdata) | |
75 | + if sendMessage_hook is not None: | |
76 | + sendMessage_hook(topicName, msgdata) | |
77 | + | |
78 | +def sendMessage_no_hook(topicName: str, **msgdata): | |
79 | + """Send a message in a given topic, but do not call the hook. | |
80 | + | |
81 | + :param topicName: | |
82 | + :param msgdata: | |
83 | + """ | |
84 | + Publisher.sendMessage(topicName, **msgdata) | |
85 | + | |
86 | +AUTO_TOPIC = Publisher.AUTO_TOPIC | ... | ... |
invesalius/reader/bitmap_reader.py
invesalius/reader/dicom_reader.py
... | ... | @@ -28,7 +28,7 @@ import gdcm |
28 | 28 | # Not showing GDCM warning and debug messages |
29 | 29 | gdcm.Trace_DebugOff() |
30 | 30 | gdcm.Trace_WarningOff() |
31 | -from pubsub import pub as Publisher | |
31 | +from invesalius.pubsub import pub as Publisher | |
32 | 32 | |
33 | 33 | import invesalius.constants as const |
34 | 34 | import invesalius.reader.dicom as dicom | ... | ... |
invesalius/session.py
invesalius/style.py
optional-requirements.txt