Skip to content

Commit

Permalink
Merge 8276080 into b38aeb3
Browse files Browse the repository at this point in the history
  • Loading branch information
DolicaAkelloEgwel committed Nov 30, 2020
2 parents b38aeb3 + 8276080 commit 0a5ede6
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 114 deletions.
62 changes: 49 additions & 13 deletions mantidimaging/gui/dialogs/cor_inspection/model.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright (C) 2020 ISIS Rutherford Appleton Laboratory UKRI
# SPDX - License - Identifier: GPL-3.0-or-later

from dataclasses import replace
from logging import getLogger
from typing import Union

from mantidimaging.core.data import Images
from mantidimaging.core.reconstruct import get_reconstructor_for
Expand All @@ -10,48 +11,83 @@

LOG = getLogger(__name__)

INIT_ITERS_CENTRE_VALUE = 100
INIT_ITERS_STEP = 50


class CORInspectionDialogModel(object):
def __init__(self, images: Images, slice_idx: int, initial_cor: ScalarCoR, recon_params: ReconstructionParameters):
def __init__(self, images: Images, slice_idx: int, initial_cor: ScalarCoR, recon_params: ReconstructionParameters,
iters_mode: bool):
self.image_width = images.width
self.sino = images.sino(slice_idx)

# Initial parameters
self.centre_cor = initial_cor.value
self.cor_step = self.image_width * 0.05
if iters_mode:
self.centre_value: Union[int, float] = INIT_ITERS_CENTRE_VALUE
self.step = INIT_ITERS_STEP
self.initial_cor = initial_cor
self._recon_preview = self._recon_iters_preview
self._divide_step = self._divide_iters_step
else:
self.centre_value = initial_cor.value
self.step = self.image_width * 0.05
self._recon_preview = self._recon_cor_preview
self._divide_step = self._divide_cor_step

# Cache projection angles
self.proj_angles = images.projection_angles(recon_params.max_projection_angle)
self.recon_params = recon_params
self.reconstructor = get_reconstructor_for(recon_params.algorithm)

def adjust_cor(self, image):
def _divide_iters_step(self):
self.step = self.step // 2

def _divide_cor_step(self):
self.step /= 2

def adjust(self, image):
"""
Adjusts the rotation centre and step after an image is selected as the
Adjusts the rotation centre/number of iterations and step after an image is selected as the
optimal of an iteration.
"""
if image == ImageType.LESS:
self.centre_cor -= self.cor_step
self.centre_value -= self.step
elif image == ImageType.MORE:
self.centre_cor += self.cor_step
self.centre_value += self.step
elif image == ImageType.CURRENT:
self.cor_step /= 2
self._divide_step()

def cor(self, image):
"""
Gets the rotation centre for a given image in the current iteration.
"""
if image == ImageType.LESS:
return max(self.cor_extents[0], self.centre_cor - self.cor_step)
return max(self.cor_extents[0], self.centre_value - self.step)
elif image == ImageType.CURRENT:
return self.centre_cor
return self.centre_value
elif image == ImageType.MORE:
return min(self.cor_extents[1], self.centre_cor + self.cor_step)
return min(self.cor_extents[1], self.centre_value + self.step)

def recon_preview(self, image):
def iterations(self, image):
if image == ImageType.LESS:
return max(1, self.centre_value - self.step)
elif image == ImageType.CURRENT:
return self.centre_value
elif image == ImageType.MORE:
return self.centre_value + self.step

def _recon_cor_preview(self, image):
cor = ScalarCoR(self.cor(image))
return self.reconstructor.single_sino(self.sino, cor, self.proj_angles, self.recon_params)

def _recon_iters_preview(self, image):
iters = self.iterations(image)
new_params = replace(self.recon_params, num_iter=iters)
return self.reconstructor.single_sino(self.sino, self.initial_cor, self.proj_angles, new_params)

def recon_preview(self, image):
return self._recon_preview(image)

@property
def cor_extents(self):
return 0, self.sino.shape[1] - 1
32 changes: 23 additions & 9 deletions mantidimaging/gui/dialogs/cor_inspection/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,15 @@ class CORInspectionDialogPresenter(BasePresenter):
view: 'CORInspectionDialogView'

def __init__(self, view, images: Images, slice_index: int, initial_cor: ScalarCoR,
recon_params: ReconstructionParameters):
recon_params: ReconstructionParameters, iters_mode: bool):
super().__init__(view)

self.model = CORInspectionDialogModel(images, slice_index, initial_cor, recon_params)
if iters_mode:
self.get_title = self._make_iters_title
else:
self.get_title = self._make_cor_title

self.model = CORInspectionDialogModel(images, slice_index, initial_cor, recon_params, iters_mode)

def notify(self, signal):
try:
Expand Down Expand Up @@ -66,32 +71,41 @@ def on_load(self):
def on_select_image(self, img):
LOG.debug('Image selected: {}'.format(img))

# Adjust COR step
self.model.adjust_cor(img)
# Adjust COR/iterations step
self.model.adjust(img)

if img != ImageType.CURRENT:
# Update UI
self.do_refresh()
else:
self.do_refresh([ImageType.LESS, ImageType.MORE])

def _make_cor_title(self, image) -> str:
return 'COR: {}'.format(self.model.cor(image))

def _make_iters_title(self, image) -> str:
return 'Iterations: {}'.format(self.model.iterations(image))

def do_refresh(self, images=None):
if images is None:
images = ImageType
# Parameters
self.view.step_size = self.model.cor_step
self.view.step_size = self.model.step

# Images
for i in ImageType:
title = 'COR: {}'.format(self.model.cor(i))
self.view.set_image(i, self.model.recon_preview(i), title)
self.view.set_image(i, self.model.recon_preview(i), self.get_title(i))

def do_update_ui_parameters(self):
self.model.cor_step = self.view.step_size
self.model.step = self.view.step_size

# Update UI
self.notify(Notification.FULL_UPDATE)

@property
def optimal_rotation_centre(self) -> ScalarCoR:
return ScalarCoR(self.model.centre_cor)
return ScalarCoR(self.model.centre_value)

@property
def optimal_iterations(self):
return self.model.centre_value
58 changes: 34 additions & 24 deletions mantidimaging/gui/dialogs/cor_inspection/test/model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,64 @@

from mantidimaging.core.utility.data_containers import ScalarCoR, ReconstructionParameters
from mantidimaging.gui.dialogs.cor_inspection import CORInspectionDialogModel
from mantidimaging.gui.dialogs.cor_inspection.model import INIT_ITERS_CENTRE_VALUE, INIT_ITERS_STEP
from mantidimaging.gui.dialogs.cor_inspection.types import ImageType
from mantidimaging.test_helpers.unit_test_helper import generate_images


class CORInspectionDialogModelTest(unittest.TestCase):
def test_construct(self):
images = generate_images()
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'))
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'), False)
npt.assert_equal(m.sino, images.sino(5))
self.assertEqual(m.cor_extents, (0, 9))
self.assertEqual(m.proj_angles.value.shape, (10, ))

def test_start_cor_step(self):
images = generate_images()
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'))
self.assertEqual(images.width * 0.05, m.cor_step)
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'), False)
self.assertEqual(images.width * 0.05, m.step)

def test_current_cor(self):
images = generate_images()
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'))
m.centre_cor = 5
m.cor_step = 1
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'), False)
m.centre_value = 5
m.step = 1
self.assertEqual(m.cor(ImageType.LESS), 4)
self.assertEqual(m.cor(ImageType.CURRENT), 5)
self.assertEqual(m.cor(ImageType.MORE), 6)

def test_adjust_cor(self):
images = generate_images()
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'))
m.centre_cor = 5
m.cor_step = 1
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'), False)
m.centre_value = 5
m.step = 1

m.adjust_cor(ImageType.CURRENT)
self.assertEqual(m.centre_cor, 5)
self.assertEqual(m.cor_step, 0.5)
m.adjust(ImageType.CURRENT)
self.assertEqual(m.centre_value, 5)
self.assertEqual(m.step, 0.5)

m.adjust_cor(ImageType.LESS)
self.assertEqual(m.centre_cor, 4.5)
self.assertEqual(m.cor_step, 0.5)
m.adjust(ImageType.LESS)
self.assertEqual(m.centre_value, 4.5)
self.assertEqual(m.step, 0.5)

m.adjust_cor(ImageType.CURRENT)
self.assertEqual(m.centre_cor, 4.5)
self.assertEqual(m.cor_step, 0.25)
m.adjust(ImageType.CURRENT)
self.assertEqual(m.centre_value, 4.5)
self.assertEqual(m.step, 0.25)

m.adjust_cor(ImageType.MORE)
self.assertEqual(m.centre_cor, 4.75)
self.assertEqual(m.cor_step, 0.25)
m.adjust(ImageType.MORE)
self.assertEqual(m.centre_value, 4.75)
self.assertEqual(m.step, 0.25)

m.adjust_cor(ImageType.CURRENT)
self.assertEqual(m.centre_cor, 4.75)
self.assertEqual(m.cor_step, 0.125)
m.adjust(ImageType.CURRENT)
self.assertEqual(m.centre_value, 4.75)
self.assertEqual(m.step, 0.125)

def test_iters_mode(self):
images = generate_images()
m = CORInspectionDialogModel(images, 5, ScalarCoR(20), ReconstructionParameters('FBP_CUDA', 'ram-lak'), True)

self.assertEqual(m.centre_value, INIT_ITERS_CENTRE_VALUE)
self.assertEqual(m.step, INIT_ITERS_STEP)
self.assertEqual(m._recon_preview, m._recon_iters_preview)
self.assertEqual(m._divide_step, m._divide_iters_step)
38 changes: 29 additions & 9 deletions mantidimaging/gui/dialogs/cor_inspection/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import List

import numpy as np
from PyQt5.QtWidgets import QPushButton, QDoubleSpinBox
from PyQt5.QtWidgets import QPushButton, QDoubleSpinBox, QSpinBox, QStackedWidget

from mantidimaging.core.data import Images
from mantidimaging.core.utility.data_containers import ScalarCoR, ReconstructionParameters
Expand All @@ -19,48 +19,68 @@ class CORInspectionDialogView(BaseDialogView):
lessButton: QPushButton
currentButton: QPushButton
moreButton: QPushButton
step: QDoubleSpinBox
stepCOR: QDoubleSpinBox
stepIterations: QSpinBox
stepStackedWidget: QStackedWidget
instructionStackedWidget: QStackedWidget

def __init__(self, parent, images: Images, slice_index: int, initial_cor: ScalarCoR,
recon_params: ReconstructionParameters):
recon_params: ReconstructionParameters, iters_mode: bool):
super().__init__(parent, 'gui/ui/cor_inspection_dialog.ui')
self.presenter = CORInspectionDialogPresenter(self, images, slice_index, initial_cor, recon_params)
self.presenter = CORInspectionDialogPresenter(self, images, slice_index, initial_cor, recon_params, iters_mode)

self.stepCOR.editingFinished.connect(lambda: self.presenter.do_update_ui_parameters())
self.stepIterations.editingFinished.connect(lambda: self.presenter.do_update_ui_parameters())

self.step.editingFinished.connect(lambda: self.presenter.do_update_ui_parameters())
self.lessButton.clicked.connect(lambda: self.presenter.on_select_image(ImageType.LESS))
self.currentButton.clicked.connect(lambda: self.presenter.on_select_image(ImageType.CURRENT))
self.moreButton.clicked.connect(lambda: self.presenter.on_select_image(ImageType.MORE))

self.finishButton.clicked.connect(self.accept)

self.stepStackedWidget.setCurrentIndex(int(iters_mode))
self.instructionStackedWidget.setCurrentIndex(int(iters_mode))

self.image_canvas = CompareSlicesView(self)
self.imagePlotLayout.addWidget(self.image_canvas)

self.presenter.do_refresh()
self.image_canvas.current_hist.imageChanged(autoLevel=True, autoRange=True)

self.iters_mode = iters_mode

def set_image(self, image_type: ImageType, recon_data: np.ndarray, title: str):
self.image_canvas.set_image(image_type, recon_data, title)

def set_maximum_cor(self, cor):
"""
Set the maximum valid rotation centre.
"""
self.step.setMaximum(cor)
self.stepCOR.setMaximum(cor)

@property
def step_size(self):
return self.step.value()
if self.iters_mode:
return self.stepIterations.value()
return self.stepCOR.value()

@step_size.setter
def step_size(self, value):
with BlockQtSignals([self.step]):
self.step.setValue(value)
if self.iters_mode:
spin_box = self.stepIterations
else:
spin_box = self.stepCOR
with BlockQtSignals([spin_box]):
self.spin_box.setValue(value)

@property
def optimal_rotation_centre(self) -> ScalarCoR:
return self.presenter.optimal_rotation_centre

@property
def optimal_iterations(self) -> int:
return self.presenter.optimal_iterations

def mark_best_recon(self, diffs):
best = diffs.index(max(diffs))
buttons: List[QPushButton] = [self.lessButton, self.currentButton, self.moreButton]
Expand Down
Loading

0 comments on commit 0a5ede6

Please sign in to comment.