diff --git a/src/Python.md b/src/Python.md index ab45fd8fdc948d989c39144523d11af2788fec32..0830555dcefcde09bd028e177df3fe018e78e959 100644 --- a/src/Python.md +++ b/src/Python.md @@ -89,6 +89,8 @@ If you are new to VTK then these [tutorials](#tutorial) will help to get you sta | -------------- | ------------- | ------- | [HDRReader](/Python/IO/HDRReader) | Read a high-dynamic-range imaging file. [ReadDICOM](/Python/IO/ReadDICOM) | Read DICOM file. +[ReadDICOMSeries](/Python/IO/ReadDICOMSeries) | This example demonstrates how to read a series of DICOM images and scroll through slices + #### Output diff --git a/src/Python/IO/ReadDICOMSeries.md b/src/Python/IO/ReadDICOMSeries.md new file mode 100644 index 0000000000000000000000000000000000000000..43e11e10c22ac8d9671d90e0c4f549948a31efcb --- /dev/null +++ b/src/Python/IO/ReadDICOMSeries.md @@ -0,0 +1,7 @@ +### Description + +This example demonstates how to read a series of DICOM images and how to scroll with the mousewheel or the up/down keys through all slices. +Sample data are available as a zipped file (977 kB, 40 slices): <a id="raw-url" href="https://raw.githubusercontent.com/Kitware/vtk-examples/gh-pages/src/SupplementaryData/Cxx/IO/DicomTestImages.zip">DicomTestImages</a> + +!!! seealso + [ReadDICOM](../ReadDICOM). \ No newline at end of file diff --git a/src/Python/IO/ReadDicomSeries.py b/src/Python/IO/ReadDicomSeries.py new file mode 100644 index 0000000000000000000000000000000000000000..c0dc2c39b8ca2271877e03d6d372fa4ae11101a1 --- /dev/null +++ b/src/Python/IO/ReadDicomSeries.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 + +# noinspection PyUnresolvedReferences +from vtkmodules.vtkCommonColor import vtkNamedColors +from vtkmodules.vtkIOImage import vtkDICOMImageReader +from vtkmodules.vtkInteractionImage import vtkImageViewer2 +from vtkmodules.vtkInteractionStyle import vtkInteractorStyleImage +import vtkmodules.vtkRenderingContextOpenGL2 +from vtkmodules.vtkRenderingCore import ( + vtkActor2D, + vtkRenderWindowInteractor, + vtkTextMapper, + vtkTextProperty + ) + +#Helper class to format slice status message +class StatusMessage: + @staticmethod + def format(slice:int,max_slice:int): + return f"Slice Number {slice+1}/{max_slice+1}" + +#Define own interaction style +class MyVtkInteractorStyleImage(vtkInteractorStyleImage): + def __init__(self,parent=None): + super().__init__() + self.AddObserver("KeyPressEvent", self.keyPressEvent) + self.AddObserver("MouseWheelForwardEvent", self.mouseWheelForwardEvent) + self.AddObserver("MouseWheelBackwardEvent", self.mouseWheelBackwardEvent) + self.imageviewer=None + self.status_mapper=None + self.slice=0 + self.min_slice=0 + self.max_slice=0 + + def set_imageviewer(self, image_viewer): + self.imageviewer=image_viewer + self.min_slice=image_viewer.GetSliceMin() + self.max_slice=image_viewer.GetSliceMax() + self.slice=self.min_slice + print(f"Slicer: Min = {self.min_slice}, Max= {self.max_slice}") + + def set_status_mapper(self, status_mapper): + self.status_mapper=status_mapper + + def move_slice_forward(self): + if self.slice<self.max_slice: + self.slice+=1 + print(f"MoveSliceForward :: Slice = {self.slice}") + self.imageviewer.SetSlice(self.slice) + msg=StatusMessage.format(self.slice,self.max_slice) + self.status_mapper.SetInput(msg) + self.imageviewer.Render() + + def move_slice_backward(self): + if self.slice>self.min_slice: + self.slice-=1 + print(f"MoveSliceBackrward :: Slice = {self.slice}") + self.imageviewer.SetSlice(self.slice) + msg=StatusMessage.format(self.slice,self.max_slice) + self.status_mapper.SetInput(msg) + self.imageviewer.Render() + + def keyPressEvent(self,obj,event): + key=self.GetInteractor().GetKeySym() + if key=="Up": + self.move_slice_forward() + elif key=="Down": + self.move_slice_backward() + + def mouseWheelForwardEvent(self,obj,event): + self.move_slice_forward() + + def mouseWheelBackwardEvent(self,obj,event) : + self.move_slice_backward() + +#Read all the DICOM files in the specified directory. +def get_program_parameters(): + import argparse + description = 'Read DICOM series data' + epilogue = '''''' + parser = argparse.ArgumentParser(description=description, epilog=epilogue, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('dirname', help='DicomTestImages.zip') + args = parser.parse_args() + return args.dirname + +def main(): + colors=vtkNamedColors() + reader=vtkDICOMImageReader() + folder=get_program_parameters() + #Read DICOM files in the specified directory + reader.SetDirectoryName(folder) + reader.Update() + + #Visualilze + image_viewer= vtkImageViewer2() + image_viewer.SetInputConnection(reader.GetOutputPort()) + #Slice status message + slice_text_prop=vtkTextProperty() + slice_text_prop.SetFontFamilyToCourier() + slice_text_prop.SetFontSize(20) + slice_text_prop.SetVerticalJustificationToBottom() + slice_text_prop.SetJustificationToLeft() + #Slice status message + slice_text_mapper=vtkTextMapper() + msg=StatusMessage.format(image_viewer.GetSliceMin(),image_viewer.GetSliceMax()) + slice_text_mapper.SetInput(msg) + slice_text_mapper.SetTextProperty(slice_text_prop) + + slice_text_actor=vtkActor2D() + slice_text_actor.SetMapper(slice_text_mapper) + slice_text_actor.SetPosition(15,10) + + #usage hint message + usage_text_prop=vtkTextProperty() + usage_text_prop.SetFontFamilyToCourier() + usage_text_prop.SetFontSize(14) + usage_text_prop.SetVerticalJustificationToTop() + usage_text_prop.SetJustificationToLeft() + usage_text_mapper=vtkTextMapper() + usage_text_mapper.SetInput( + "Slice with mouse wheel\n or Up/Down-Key\n- Zoom with pressed right\n " + " mouse button while dragging" + ) + usage_text_mapper.SetTextProperty(usage_text_prop) + + usage_text_actor=vtkActor2D () + usage_text_actor.SetMapper(usage_text_mapper) + usage_text_actor.GetPositionCoordinate().SetCoordinateSystemToNormalizedDisplay() + usage_text_actor.GetPositionCoordinate().SetValue(0.05, 0.95) + + #Create an interactor with our own style (inherit from + #vtkInteractorStyleImage in order to catch mousewheel and key events. + render_window_interactor= vtkRenderWindowInteractor() + my_interactor_style=MyVtkInteractorStyleImage() + + #Make imageviewer2 and sliceTextMapper visible to our interactorstyle + #to enable slice status message updates when scrolling through the slices. + my_interactor_style.set_imageviewer(image_viewer) + my_interactor_style.set_status_mapper(slice_text_mapper) + + #Make the interactor use our own interactorstyle + #cause SetupInteractor() is defining it's own default interatorstyle + #this must be called after SetupInteractor(). + #renderWindowInteractor.SetInteractorStyle(myInteractorStyle); + image_viewer.SetupInteractor(render_window_interactor) + render_window_interactor.SetInteractorStyle(my_interactor_style) + render_window_interactor.Render() + + #Add slice status message and usage hint message to the renderer. + image_viewer.GetRenderer().AddActor2D(slice_text_actor) + image_viewer.GetRenderer().AddActor2D(usage_text_actor) + + # Initialize rendering and interaction. + image_viewer.Render() + image_viewer.GetRenderer().ResetCamera() + image_viewer.GetRenderer().SetBackground(colors.GetColor3d("SlateGray")) + image_viewer.GetRenderWindow().SetSize(800, 800) + image_viewer.GetRenderWindow().SetWindowName("ReadDICOMSeries") + image_viewer.Render() + render_window_interactor.Start() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/Testing/Baseline/Python/IO/TestReadDICOMSeries.png b/src/Testing/Baseline/Python/IO/TestReadDICOMSeries.png new file mode 100644 index 0000000000000000000000000000000000000000..c10bf1d9d039aa494c43916cb13a27713d6b6cf9 --- /dev/null +++ b/src/Testing/Baseline/Python/IO/TestReadDICOMSeries.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9abfef71e88a2dc08e8d4097bda52a16a1758aaf8b6edf6df7031270e0a129fc +size 128807