|
22 | 22 | "source": [
|
23 | 23 | "import SimpleITK as sitk\n",
|
24 | 24 | "import numpy as np\n",
|
| 25 | + "import itertools\n", |
25 | 26 | "\n",
|
26 | 27 | "%matplotlib inline\n",
|
27 | 28 | "import gui\n",
|
|
874 | 875 | {
|
875 | 876 | "cell_type": "code",
|
876 | 877 | "execution_count": null,
|
877 |
| - "metadata": {}, |
| 878 | + "metadata": { |
| 879 | + "scrolled": true |
| 880 | + }, |
878 | 881 | "outputs": [],
|
879 | 882 | "source": [
|
880 | 883 | "def resize_and_scale_uint8(image, new_size, outside_pixel_value=0):\n",
|
|
930 | 933 | "# Select the arbitrary new size\n",
|
931 | 934 | "new_size = [128, 128]\n",
|
932 | 935 | "resized_images = [resize_and_scale_uint8(image, new_size, 50) for image in images]\n",
|
933 |
| - "gui.multi_image_display2D(resized_images);" |
| 936 | + "gui.multi_image_display2D(resized_images, figure_size=[8, 4]);" |
| 937 | + ] |
| 938 | + }, |
| 939 | + { |
| 940 | + "cell_type": "markdown", |
| 941 | + "metadata": {}, |
| 942 | + "source": [ |
| 943 | + "### Resampling after registration\n", |
| 944 | + "\n", |
| 945 | + "The result of registration is a transformation which maps points from the fixed image coordinate system to the moving image coordinate system. Most often, we then resample the moving image onto the fixed image grid using this transformation.\n", |
| 946 | + "\n", |
| 947 | + "This implicitly assumes that the information we are interested in is contained only in the overlapping regions. In some instances, this is not the case. For example, when registration is used to align multiple images to create a composite image which has a larger physical extent. This is common in microscopy where multiple tiles are combined to create the full field of view (the interested reader is referred to the [ITKMontage module](https://github.com/InsightSoftwareConsortium/ITKMontage), not available in SimpleITK).\n", |
| 948 | + "\n", |
| 949 | + "In the cells below we illustrate how to combine the result of registration using the common approach, resampling onto the fixed image grid and onto a grid that contains all image data. The only difference between these two is the definition of the resampling grid. Thus, resampling using the same registration result can yield significantly different images." |
| 950 | + ] |
| 951 | + }, |
| 952 | + { |
| 953 | + "cell_type": "code", |
| 954 | + "execution_count": null, |
| 955 | + "metadata": {}, |
| 956 | + "outputs": [], |
| 957 | + "source": [ |
| 958 | + "# Create an artificial registration dataset and result.\n", |
| 959 | + "image = sitk.ReadImage(fdata(\"cthead1.png\"))\n", |
| 960 | + "fixed_image = image[80:200, 100:250]\n", |
| 961 | + "moving_image = image[20:150, 60:160]\n", |
| 962 | + "registration_result = sitk.Euler2DTransform([50, 60], -0.785, [70, 80])\n", |
| 963 | + "# Modify the moving image origin and direction cosine using the\n", |
| 964 | + "# \"registration\" transformation and then add noise to the transformation\n", |
| 965 | + "# mimicking registration error.\n", |
| 966 | + "moving_image.SetOrigin(registration_result.TransformPoint(moving_image.GetOrigin()))\n", |
| 967 | + "moving_image.SetDirection(registration_result.GetMatrix())\n", |
| 968 | + "registration_result.SetTranslation(\n", |
| 969 | + " [t + np.random.random() * 5 for t in registration_result.GetTranslation()]\n", |
| 970 | + ")\n", |
| 971 | + "gui.multi_image_display2D(\n", |
| 972 | + " [fixed_image, moving_image], [\"fixed image\", \"moving image\"], figure_size=[5, 4]\n", |
| 973 | + ");" |
| 974 | + ] |
| 975 | + }, |
| 976 | + { |
| 977 | + "cell_type": "code", |
| 978 | + "execution_count": null, |
| 979 | + "metadata": {}, |
| 980 | + "outputs": [], |
| 981 | + "source": [ |
| 982 | + "# Resample the moving image onto the fixed image grid, notice that parts of the original moving image are not included\n", |
| 983 | + "# because they are outside the physical region occupied by the fixed image.\n", |
| 984 | + "resampled_moving = sitk.Resample(moving_image, fixed_image, registration_result)\n", |
| 985 | + "\n", |
| 986 | + "# Use green/magenta color scheme, image is gray in overlap regions where the registration is aligned.\n", |
| 987 | + "gui.multi_image_display2D(\n", |
| 988 | + " [\n", |
| 989 | + " sitk.Cast(\n", |
| 990 | + " sitk.Compose(fixed_image, resampled_moving, fixed_image),\n", |
| 991 | + " sitk.sitkVectorUInt8,\n", |
| 992 | + " )\n", |
| 993 | + " ],\n", |
| 994 | + " [\"combined image\"],\n", |
| 995 | + " figure_size=[4, 4],\n", |
| 996 | + ");" |
| 997 | + ] |
| 998 | + }, |
| 999 | + { |
| 1000 | + "cell_type": "code", |
| 1001 | + "execution_count": null, |
| 1002 | + "metadata": {}, |
| 1003 | + "outputs": [], |
| 1004 | + "source": [ |
| 1005 | + "# Compute the axis aligned bounding box grid for all images. Treat all images the same way, so\n", |
| 1006 | + "# code is concise and applicable to N images of the same dimension, 2D or 3D.\n", |
| 1007 | + "# The choice of spacing is arbitrary and implicitly defines the size of the grid.\n", |
| 1008 | + "images = [fixed_image, moving_image]\n", |
| 1009 | + "transforms = [sitk.Transform(2, sitk.sitkIdentity), registration_result]\n", |
| 1010 | + "dim = images[0].GetDimension()\n", |
| 1011 | + "\n", |
| 1012 | + "boundary_points = []\n", |
| 1013 | + "for image, transform in zip(images, transforms):\n", |
| 1014 | + " for boundary_index in list(\n", |
| 1015 | + " itertools.product(*zip([0] * dim, [sz - 1 for sz in image.GetSize()]))\n", |
| 1016 | + " ): # Points from the moving image(s) are mapped to the fixed coordinate system using the inverse\n", |
| 1017 | + " # of the registration_result.\n", |
| 1018 | + " boundary_points.append(\n", |
| 1019 | + " transform.GetInverse().TransformPoint(\n", |
| 1020 | + " image.TransformIndexToPhysicalPoint(boundary_index)\n", |
| 1021 | + " )\n", |
| 1022 | + " )\n", |
| 1023 | + "max_coords = np.max(boundary_points, axis=0)\n", |
| 1024 | + "min_coords = np.min(boundary_points, axis=0)\n", |
| 1025 | + "\n", |
| 1026 | + "new_origin = min_coords\n", |
| 1027 | + "# Arbitrarily use the spacing of the first image and its pixel type,\n", |
| 1028 | + "# change these to suite your needs.\n", |
| 1029 | + "new_spacing = images[0].GetSpacing()\n", |
| 1030 | + "new_pixel_type = images[0].GetPixelID()\n", |
| 1031 | + "new_size = (((max_coords - min_coords) / new_spacing).round().astype(int)).tolist()\n", |
| 1032 | + "new_direction = np.identity(dim).ravel()\n", |
| 1033 | + "\n", |
| 1034 | + "# Resample all images onto the common grid.\n", |
| 1035 | + "resampled_images = []\n", |
| 1036 | + "for image, transform in zip(images, transforms):\n", |
| 1037 | + " resampled_images.append(\n", |
| 1038 | + " sitk.Resample(\n", |
| 1039 | + " image,\n", |
| 1040 | + " new_size,\n", |
| 1041 | + " transform,\n", |
| 1042 | + " sitk.sitkLinear,\n", |
| 1043 | + " new_origin,\n", |
| 1044 | + " new_spacing,\n", |
| 1045 | + " new_direction,\n", |
| 1046 | + " 0.0,\n", |
| 1047 | + " new_pixel_type,\n", |
| 1048 | + " )\n", |
| 1049 | + " )\n", |
| 1050 | + "\n", |
| 1051 | + "# Use green/magenta color scheme, image is gray in overlap regions where the registration is aligned\n", |
| 1052 | + "gui.multi_image_display2D(\n", |
| 1053 | + " [\n", |
| 1054 | + " sitk.Cast(\n", |
| 1055 | + " sitk.Compose(resampled_images[0], resampled_images[1], resampled_images[0]),\n", |
| 1056 | + " sitk.sitkVectorUInt8,\n", |
| 1057 | + " )\n", |
| 1058 | + " ],\n", |
| 1059 | + " [\"combined image\"],\n", |
| 1060 | + " figure_size=[4, 4],\n", |
| 1061 | + ");" |
934 | 1062 | ]
|
935 | 1063 | }
|
936 | 1064 | ],
|
|
0 commit comments