Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:RBVI/ChimeraX into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
elainecmeng committed Nov 5, 2023
2 parents b75cfe2 + 2753246 commit 2b2a739
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 77 deletions.
6 changes: 5 additions & 1 deletion src/bundles/core/src/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,12 +900,16 @@ def init(argv, event_loop=True):
sess.ui.open_pending_files(ignore_files=(args if bad_drop_events else []))

# By this point the GUI module will have redirected stdout if it's going to
if bool(os.getenv("DEBUG")):
if opts.debug:
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s:%(message)s",
handlers=[logging.StreamHandler(sys.stdout)]
)
else:
# Ticket #6187, set urllib3 not to log to the general ChimeraX log
# It's just very verbose on failure.
logging.getLogger('urllib3').setLevel(100)

# Allow the event_loop to be disabled, so we can be embedded in
# another application
Expand Down
13 changes: 6 additions & 7 deletions src/bundles/core/src/toolshed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1533,21 +1533,20 @@ def __init__(self, session):

def run(self, service_name, params, blocking=False):
self.result = self.api.check_for_updates(**params, async_req=not blocking)

def on_finish(self):
# If async_req is True, then need to call self.result.get()
try:
versions = self.result.get()
self.versions = self.result.get()
except Exception:
# Ignore problems getting results. Might be a network error or
# a server error. It doesn't matter, just let ChimeraX run.
return
if not versions:
self.versions = None

def on_finish(self):
if self.versions is None:
return

# don't bother user about releases they've choosen to ignore
from ..core_settings import settings
versions = [v for v in versions if v[0] not in settings.ignore_update]
versions = [v for v in self.versions if v[0] not in settings.ignore_update]
if not versions:
return

Expand Down
88 changes: 55 additions & 33 deletions src/bundles/dicom/src/dicom/dicom_hierarchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ def series_from_files(self, files) -> None:
else:
self.name = '%s Study (Unknown Date)' % (self.body_part)
self.series.sort(key=lambda s: s.sort_key)

plane_ids = {s.plane_uids: s for s in self.series}
for s in self.series:
ref = s.ref_plane_uids
if ref and ref in plane_ids:
s.refers_to_series = plane_ids[ref]

@requires_gui
def show_info(self):
Expand All @@ -179,10 +183,13 @@ def merge_and_delete_other(self, other):

def open_series_as_models(self):
if self.series:
derived = []
sgrids = {}
for s in self.series:
try:
if s.uid not in self._drawn_series:
self.add(s.to_models())
models = s.to_models(derived, sgrids)
self.add(models)
self._drawn_series.add(s.uid)
except UnrenderableSeriesError as e:
self.session.logger.warning(str(e))
Expand Down Expand Up @@ -245,6 +252,7 @@ def __init__(self, session, parent, files):
self.parent_study = parent
self.files = files
self.sample_file = self.files[0]
self._refers_to_series = None
self.files_by_size = defaultdict(list)
self.dicom_data = []
for f in self.files:
Expand All @@ -259,18 +267,24 @@ def __init__(self, session, parent, files):
self.dicom_data.append(DicomData(self.session, self, file_list))
else:
self.dicom_data.append(DicomData(self.session, self, file_list))
#plane_ids = {s.plane_uids: s for s in self.series}
#for s in self.series:
# ref = s.ref_plane_uids
# if ref and ref in plane_ids:
# s.refers_to_series = plane_ids[ref]

def to_models(self):
def to_models(self, derived, sgrids):
models = []
for data in self.dicom_data:
models.extend(data.to_models())
grids = data.to_models(derived, sgrids)
models.extend(grids)
return models

@property
def refers_to_series(self):
return self._refers_to_series

@refers_to_series.setter
def refers_to_series(self, series):
self._refers_to_series = series
for data in self.dicom_data:
data.refers_to_series = series

@property
def name(self):
return f"{self.number} {self.modality} ({self.description})"
Expand Down Expand Up @@ -377,21 +391,21 @@ def sort_key(self):

@property
def plane_uids(self):
uids = set()
uids = []
for data in self.dicom_data:
uids.add(fi.instance_uid for fi in data.files)
uids.extend([fi.instance_uid for fi in data.files])
return tuple(uids)

@property
def ref_plane_uids(self):
uids = set()
uids = []
for data in self.dicom_data:
if len(data.files) == 1 and hasattr(data.sample_file, 'ref_instance_uids'):
_uids = data.files[0].ref_instance_uids
if _uids:
for uid in _uids:
uids.add(uid)
return uids if uids else None
uids.append(uid)
return tuple(uids) if uids else None


@property
Expand All @@ -417,6 +431,7 @@ def __init__(self, session, series, files: list['SeriesFile'], mask_number: Opti
self.order_slices()
self.sample_file = files[0]
self.paths = tuple(file.path for file in files)
self._refers_to_series = None
self.transfer_syntax = None
self._multiframe = None
self._num_times = None
Expand All @@ -434,7 +449,6 @@ def __init__(self, session, series, files: list['SeriesFile'], mask_number: Opti
# self.z_plane_spacing()
# Check that time series images all have time value, and all times are found
self._validate_time_series()

npaths = len(self.paths) # noqa assigned but not accessed
self.name = series.name
if self.mask_number is not None:
Expand Down Expand Up @@ -489,10 +503,10 @@ def __init__(self, session, series, files: list['SeriesFile'], mask_number: Opti
# Check that time series images all have time value, and all times are found
self._validate_time_series()

def _to_grids(self) -> list['DicomGrid']:
def _to_grids(self, derived, sgrids) -> list['DicomGrid']:
grids = []
derived = [] # For grouping derived series with original series
sgrids = {}
# derived = [] # For grouping derived series with original series
# sgrids = {}
if self.mode == 'RGB':
# Create 3-channels for RGB series
cgrids = []
Expand Down Expand Up @@ -525,33 +539,41 @@ def _to_grids(self) -> list['DicomGrid']:
if rs:
# If this associated with another series (e.g. is a segmentation), make
# it a channel together with that associated series.
derived.append((g, rs))
# the bool is whether it's been opened
derived.append([g, rs, False])
else:
sgrids[self] = gg = [g]
sgrids[self.dicom_series] = gg = [g]
grids.extend(gg)
# Group derived series with the original series
# channel_colors = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)]
# for g, rs in derived:
# sg = self.
# if len(sg) == 1:
# sg[0].channel = 1
# sg.append(g)
# g.channel = len(sg)
# g.rgba = channel_colors[(g.channel - 2) % len(channel_colors)]
# if not g.dicom_data.origin_specified:
# # Segmentation may not have specified an origin
# g.set_origin(sg[0].origin)
channel_colors = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)]
for index, (g, rs, _) in enumerate(derived):
#open_series = set([s.dicom_series for s in sgrids.keys()])
if rs in sgrids:
source_grid = sgrids[rs]
#if len(sg) == 1:
# sg[0].channel = 1
#sg.append(g)
#g.channel = len(sg)
#g.rgba = channel_colors[(g.channel - 2) % len(channel_colors)]
#if not g.dicom_data.origin_specified:
# # Segmentation may not have specified an origin
# g.set_origin(sg[0].origin)
g.reference_data = source_grid[0]
if not derived[index][2]:
grids.append(g)
derived[index][2] = True
# Show only first group of grids
# for gg in grids[1:]:
# for g in gg:
# g.show_on_open = False
return grids

def to_models(self):
def to_models(self, derived, sgrids):
if self.contour_series:
return [DicomContours(self.session, s, self.name) for s in self.files]
elif self.image_series:
return open_dicom_grids(self.session, self._to_grids(), name=self.name)[0]
grids = self._to_grids(derived, sgrids)
return open_dicom_grids(self.session, grids, name=self.name)[0]
else:
raise UnrenderableSeriesError("No model created for Series #%s from patient %s because "
"it had no pixel data. Metadata will still "
Expand Down
64 changes: 45 additions & 19 deletions src/bundles/dicom/src/ui/segmentations.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ class ViewMode(IntEnum):

def __str__(self):
if self.name == "FOUR_UP":
return "2 x 2 (Desktop)"
return "2 x 2 (desktop)"
elif self.name == "ORTHOPLANES_OVER_3D":
return "3D over slices (desktop)"
elif self.name == "ORTHOPLANES_BESIDE_3D":
return "3D beside slices (Desktop)"
return "3D beside slices (desktop)"
elif self.name == "DEFAULT_DESKTOP":
return "3D only (Desktop)"
return "3D only (desktop)"
elif self.name == "DEFAULT_VR":
return "3D only (VR)"
return "%s: Set a value to return for the name of this EnumItem" % self.name
Expand Down Expand Up @@ -174,12 +174,12 @@ def __init__(self, parent, settings):
self._add_mouse_3d_tab()
self._add_vr_tab()
self.button_widget = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
QDialogButtonBox.StandardButton.Save | QDialogButtonBox.StandardButton.Reset | QDialogButtonBox.StandardButton.RestoreDefaults
, self
)
self.button_widget.accepted.connect(self.accept)
self.button_widget.accepted.connect(self._on_accept)
self.button_widget.rejected.connect(self.reject)
self.button_widget.button(QDialogButtonBox.StandardButton.Reset).clicked.connect(self._on_reset)
self.button_widget.button(QDialogButtonBox.StandardButton.RestoreDefaults).clicked.connect(self._on_restore_defaults)
self.layout().addWidget(self.button_widget)

def _on_accept(self):
Expand All @@ -200,21 +200,40 @@ def _on_accept(self):
self.cx_settings.default_segmentation_opacity = self.default_opacity_spinbox.value()
self.cx_settings.save()

def _on_reset(self):
self.default_view_dropdown.setCurrentIndex(self.cx_settings.default_view)
self.file_format_dropdown.setCurrentIndex(self.cx_settings.default_file_format)
self.default_opacity_spinbox.setValue(self.cx_settings.default_segmentation_opacity)
self.set_mouse_modes_checkbox.setChecked(self.cx_settings.set_mouse_modes_automatically)
self.set_hand_modes_checkbox.setChecked(self.cx_settings.set_hand_modes_automatically)
self.start_vr_checkbox.setChecked(self.cx_settings.start_vr_automatically)

def _on_restore_defaults(self):
for key, value in DEFAULT_SETTINGS.items():
setattr(self.cx_settings, key, value)
self.cx_settings.save()
self.default_view_dropdown.setCurrentIndex(self.cx_settings.default_view)
self.file_format_dropdown.setCurrentIndex(self.cx_settings.default_file_format)
self.default_opacity_spinbox.setValue(self.cx_settings.default_segmentation_opacity)
self.set_mouse_modes_checkbox.setChecked(self.cx_settings.set_mouse_modes_automatically)
self.set_hand_modes_checkbox.setChecked(self.cx_settings.set_hand_modes_automatically)
self.start_vr_checkbox.setChecked(self.cx_settings.start_vr_automatically)

def _add_settings_tab(self):
self.settings_container = QWidget(self)
self.settings_container.setLayout(QVBoxLayout())
self.start_vr_checkbox = QCheckBox("Start VR Automatically when 3D VR view mode chosen")
self.start_vr_checkbox = QCheckBox("Start VR when the VR layout is chosen")
self.start_vr_checkbox.setChecked(self.cx_settings.start_vr_automatically)
self.set_mouse_modes_checkbox = QCheckBox("Set 3D Mouse Modes Automatically when 3D Desktop View Chosen")
self.set_mouse_modes_checkbox = QCheckBox("Set 3D mouse modes when the desktop 3D-only layout is chosen")
self.set_mouse_modes_checkbox.setChecked(self.cx_settings.set_mouse_modes_automatically)
self.set_hand_modes_checkbox = QCheckBox("Set VR Hand Modes Automatically when 3D VR View Chosen")
self.set_hand_modes_checkbox = QCheckBox("Set VR controller modes when the VR layout is chosen")
self.set_hand_modes_checkbox.setChecked(self.cx_settings.set_hand_modes_automatically)
self.default_view_dropdown_container = QWidget(self.settings_container)
self.default_view_dropdown_layout = QHBoxLayout()
self.default_view_dropdown_container.setLayout(self.default_view_dropdown_layout)
self.default_view_dropdown_layout.setContentsMargins(0, 0, 0, 0)
self.default_view_dropdown_layout.setSpacing(0)
self.default_view_dropdown_label = QLabel("Default View:")
self.default_view_dropdown_label = QLabel("Default layout:")
self.default_view_dropdown = QComboBox(self)
for view in ViewMode:
self.default_view_dropdown.addItem(str(view))
Expand Down Expand Up @@ -248,28 +267,28 @@ def _add_settings_tab(self):
self.default_opacity_spinbox = QSpinBox(self.settings_container)
self.default_opacity_spinbox.setRange(0, 100)
self.default_opacity_spinbox.setSuffix("%")
self.default_opacity_spinbox.setValue(self.cx_settings.default_segmentation_opacity or 70)
self.default_opacity_spinbox.setValue(self.cx_settings.default_segmentation_opacity)
self.default_opacity_spinbox_layout.addWidget(self.default_opacity_spinbox_label)
self.default_opacity_spinbox_layout.addSpacing(8)
self.default_opacity_spinbox_layout.addWidget(self.default_opacity_spinbox)
self.default_opacity_spinbox_layout.addStretch()

self.settings_container.layout().addWidget(self.start_vr_checkbox)
self.settings_container.layout().addWidget(self.default_view_dropdown_container)
self.settings_container.layout().addWidget(self.set_mouse_modes_checkbox)
self.set_mouse_modes_checkbox.setToolTip("Replaced mouse modes will be restored when the tool closes or the view is changed.")
self.settings_container.layout().addWidget(self.start_vr_checkbox)
self.settings_container.layout().addWidget(self.set_hand_modes_checkbox)
self.set_hand_modes_checkbox.setToolTip("Replaced hand modes will be restored when the tool closes.")
self.settings_container.layout().addWidget(self.default_view_dropdown_container)
self.settings_container.layout().addWidget(self.default_opacity_spinbox_container)
self.settings_container.layout().addSpacing(-2)
self.settings_container.layout().addWidget(self.file_format_dropdown_container)
self.dicom_format_explanatory_text = QLabel("DICOM metadata will be lost when saving DICOM segmentations in NIfTI or NRRD format.")
self.dicom_format_explanatory_text = QLabel("DICOM metadata will be lost if NIfTI or NRRD format is used.")
self.dicom_format_explanatory_text.setWordWrap(True)
shrink_font(self.dicom_format_explanatory_text, 0.9)
self.settings_container.layout().addSpacing(-10)
self.settings_container.layout().addWidget(self.dicom_format_explanatory_text)
self.settings_container.layout().addSpacing(2)
self.settings_container.layout().addWidget(self.default_opacity_spinbox_container)
self.settings_container.layout().addStretch()
self.tab_widget.addTab(self.settings_container, "General Settings")
self.tab_widget.addTab(self.settings_container, "General")

def _add_mouse_2d_tab(self):
self.mouse_2d_outer_widget = QWidget(self)
Expand Down Expand Up @@ -467,7 +486,7 @@ def _construct_ui(self):

self.view_dropdown_container = QWidget(self.parent)
self.view_dropdown_layout = QHBoxLayout()
self.view_dropdown_label = QLabel("View Layout")
self.view_dropdown_label = QLabel("View layout")
self.view_dropdown = QComboBox(self.parent)
for view in ViewMode:
self.view_dropdown.addItem(str(view))
Expand Down Expand Up @@ -557,7 +576,7 @@ def _not_volume_surface_or_segmentation(m):
self.control_checkbox_container = QWidget()
self.control_checkbox_layout = QHBoxLayout()

self.guidelines_checkbox = QCheckBox("Plane Guidelines")
self.guidelines_checkbox = QCheckBox("Plane guidelines")
self.control_checkbox_layout.addWidget(self.model_menu.frame)
self.control_checkbox_layout.addWidget(self.guidelines_checkbox)
self.guidelines_checkbox.stateChanged.connect(self._on_show_guidelines_checkbox_changed)
Expand Down Expand Up @@ -661,8 +680,14 @@ def _not_volume_surface_or_segmentation(m):
if self.session.ui.main_window.view_layout == "orthoplanes":
self.session.ui.main_window.main_view.add_segmentation(model)
self.segmentations_by_model = {}
self.tool_window.fill_context_menu = self.fill_context_menu
self._surface_chosen()

def fill_context_menu(self, menu, x, y):
show_settings_action = QAction("Settings...", menu)
show_settings_action.triggered.connect(self.showControlsDialog)
menu.addAction(show_settings_action)

def showControlsDialog(self):
self.controls_dialog.show()

Expand Down Expand Up @@ -700,6 +725,7 @@ def delete(self):
# fail gracefully if the models have already been deleted
try:
self._destroy_2d_segmentation_pucks()
self._destroy_3d_segmentation_sphere()
except TypeError:
pass
self._reset_3d_mouse_modes()
Expand Down
Loading

0 comments on commit 2b2a739

Please sign in to comment.