class ReconImagesView(GraphicsLayoutWidget): def __init__(self, parent): super().__init__(parent) self.parent = parent self.projection, self.projection_vb, self.projection_hist = self.image_in_vb("Projection") self.sinogram, self.sinogram_vb, self.sinogram_hist = self.image_in_vb("Sinogram") self.recon, self.recon_vb, self.recon_hist = self.image_in_vb("Recon") self.slice_line = InfiniteLine(pos=1024, angle=0, bounds=[0, self.projection.width()]) self.projection_vb.addItem(self.slice_line) self.tilt_line = InfiniteLine(pos=1024, angle=90, pen=(255, 0, 0, 255), movable=True) image_layout = self.addLayout(colspan=4) image_layout.addItem(self.projection_vb, 0, 0) image_layout.addItem(self.projection_hist, 0, 1) image_layout.addItem(self.recon_vb, 0, 2, rowspan=3) image_layout.addItem(self.recon_hist, 0, 3, rowspan=3) projection_details = LabelItem("Value") image_layout.addItem(projection_details, 1, 0, 1, 2) image_layout.addItem(self.sinogram_vb, 2, 0) image_layout.addItem(self.sinogram_hist, 2, 1) sino_details = LabelItem("Value") image_layout.addItem(sino_details, 3, 0, 1, 2) recon_details = LabelItem("Value") image_layout.addItem(recon_details, 3, 2, 1, 2) msg_format = "Value: {:.6f}" self.display_formatted_detail = { self.projection: lambda val: projection_details.setText(msg_format.format(val)), self.sinogram: lambda val: sino_details.setText(msg_format.format(val)), self.recon: lambda val: recon_details.setText(msg_format.format(val)) } self.projection.hoverEvent = lambda ev: self.mouse_over(ev, self.projection) self.projection.mouseClickEvent = lambda ev: self.mouse_click(ev, self.slice_line) self.sinogram.hoverEvent = lambda ev: self.mouse_over(ev, self.sinogram) self.recon.hoverEvent = lambda ev: self.mouse_over(ev, self.recon) @staticmethod def image_in_vb(name=None) -> Tuple[ImageItem, ViewBox, HistogramLUTItem]: im = ImageItem() vb = ViewBox(invertY=True, lockAspect=True, name=name) vb.addItem(im) hist = HistogramLUTItem(im) return im, vb, hist def update_projection(self, image_data: numpy.ndarray, preview_slice_index: int, tilt_angle: Optional[Degrees]): self.projection.clear() self.projection.setImage(image_data) self.projection_hist.imageChanged(autoLevel=True, autoRange=True) self.slice_line.setPos(preview_slice_index) if tilt_angle: self.set_tilt(tilt_angle, image_data.shape[1] // 2) else: self.hide_tilt() def update_sinogram(self, image): self.sinogram.clear() self.sinogram.setImage(image) self.sinogram_hist.imageChanged(autoLevel=True, autoRange=True) def update_recon(self, image_data, refresh_recon_slice_histogram): self.recon.clear() self.recon.setImage(image_data, autoLevels=refresh_recon_slice_histogram) if refresh_recon_slice_histogram: self.recon_hist.imageChanged(autoLevel=True, autoRange=True) def mouse_over(self, ev, img): # Ignore events triggered by leaving window or right clicking if ev.exit: return pos = CloseEnoughPoint(ev.pos()) if img.image is not None and pos.x < img.image.shape[0] and pos.y < img.image.shape[1]: pixel_value = img.image[pos.y, pos.x] self.display_formatted_detail[img](pixel_value) def mouse_click(self, ev, line: InfiniteLine): line.setPos(ev.pos()) # don't refresh the histogram on click to stop the contrast for re-adjusting for each slice # it's much easier to see what's happening to the reconstruction if the slice doesn't # reset after every click self.parent.presenter.do_reconstruct_slice(slice_idx=CloseEnoughPoint(ev.pos()).y, refresh_recon_slice_histogram=False) def clear_recon(self): self.recon.clear() def reset_slice_and_tilt(self, slice_index): self.slice_line.setPos(slice_index) self.hide_tilt() def hide_tilt(self): """ Hides the tilt line. This stops infinite zooming out loop that messes up the image view (the line likes to be unbound when the degree isn't a multiple o 90 - and the tilt never is) :return: """ self.projection_vb.removeItem(self.tilt_line) def set_tilt(self, tilt: Degrees, pos: Optional[int] = None): if not isnan(tilt.value): # is isnan it means there is no tilt, i.e. the line is vertical if pos is not None: self.tilt_line.setAngle(90) self.tilt_line.setPos(pos) self.tilt_line.setAngle(90 + tilt.value) self.projection_vb.addItem(self.tilt_line)
class ReconImagesView(GraphicsLayoutWidget): sigSliceIndexChanged = QtCore.pyqtSignal(int) def __init__(self, parent): super().__init__(parent) self.parent = parent self.imageview_projection = MIMiniImageView(name="Projection") self.imageview_sinogram = MIMiniImageView(name="Sinogram") self.imageview_recon = MIMiniImageView(name="Recon") self.projection, self.projection_vb, self.projection_hist = self.imageview_projection.get_parts( ) self.sinogram, self.sinogram_vb, self.sinogram_hist = self.imageview_sinogram.get_parts( ) self.recon, self.recon_vb, self.recon_hist = self.imageview_recon.get_parts( ) self.slice_line = InfiniteLine( pos=1024, angle=0, bounds=[0, self.imageview_projection.image_item.width()], movable=True) self.projection_vb.addItem(self.slice_line) self.tilt_line = InfiniteLine(pos=1024, angle=90, pen=(255, 0, 0, 255), movable=True) self.addItem(self.imageview_projection, 0, 0) self.addItem(self.imageview_recon, 0, 1, rowspan=2) self.addItem(self.imageview_sinogram, 1, 0) self.imageview_projection.image_item.mouseClickEvent = lambda ev: self.mouse_click( ev, self.slice_line) self.slice_line.sigPositionChangeFinished.connect( self.slice_line_moved) # Work around for https://github.com/mantidproject/mantidimaging/issues/565 self.scene().contextMenu = [ item for item in self.scene().contextMenu if "export" not in item.text().lower() ] self.imageview_projection.enable_nan_check() self.imageview_sinogram.enable_nan_check() self.imageview_recon.enable_nan_check() self.imageview_projection.enable_nonpositive_check() self.imageview_sinogram.enable_nonpositive_check() def slice_line_moved(self): self.slice_changed(int(self.slice_line.value())) def update_projection(self, image_data: np.ndarray, preview_slice_index: int, tilt_angle: Optional[Degrees]): self.imageview_projection.clear() self.imageview_projection.setImage(image_data) self.projection_hist.imageChanged(autoLevel=True, autoRange=True) self.slice_line.setPos(preview_slice_index) if tilt_angle: self.set_tilt(tilt_angle, image_data.shape[1] // 2) else: self.hide_tilt() set_histogram_log_scale(self.projection_hist) def update_sinogram(self, image): self.imageview_sinogram.clear() self.imageview_sinogram.setImage(image) self.sinogram_hist.imageChanged(autoLevel=True, autoRange=True) set_histogram_log_scale(self.sinogram_hist) def update_recon(self, image_data): self.imageview_recon.clear() self.imageview_recon.setImage(image_data, autoLevels=False) set_histogram_log_scale(self.recon_hist) def update_recon_hist(self): self.recon_hist.imageChanged(autoLevel=True, autoRange=True) def mouse_click(self, ev, line: InfiniteLine): line.setPos(ev.pos()) self.slice_changed(CloseEnoughPoint(ev.pos()).y) def slice_changed(self, slice_index): self.parent.presenter.do_preview_reconstruct_slice( slice_idx=slice_index) self.sigSliceIndexChanged.emit(slice_index) def clear_recon(self): self.imageview_recon.clear() def reset_slice_and_tilt(self, slice_index): self.slice_line.setPos(slice_index) self.hide_tilt() def hide_tilt(self): """ Hides the tilt line. This stops infinite zooming out loop that messes up the image view (the line likes to be unbound when the degree isn't a multiple o 90 - and the tilt never is) :return: """ if self.tilt_line.scene() is not None: self.projection_vb.removeItem(self.tilt_line) def set_tilt(self, tilt: Degrees, pos: Optional[int] = None): if not isnan( tilt.value ): # is isnan it means there is no tilt, i.e. the line is vertical if pos is not None: self.tilt_line.setAngle(90) self.tilt_line.setPos(pos) self.tilt_line.setAngle(90 + tilt.value) self.projection_vb.addItem(self.tilt_line) def reset_recon_histogram(self): self.recon_hist.autoHistogramRange()