예제 #1
0
        /// <summary>
        /// Builds a 3-dimensional volume from the provided volume information.
        /// This method will parallelise voxel extraction per slice.
        /// </summary>
        /// <param name="volumeInformation">The volume information.</param>
        /// <param name="maxDegreeOfParallelism">The maximum degrees of parallelism when extracting voxel data from the DICOM datasets.</param>
        /// <returns>The 3-dimensional volume.</returns>
        /// <exception cref="ArgumentNullException">The provided volume information was null.</exception>
        /// <exception cref="InvalidOperationException">The decoded DICOM pixel data was not the expected length.</exception>
        public static Volume3D <short> BuildVolume(
            VolumeInformation volumeInformation,
            uint maxDegreeOfParallelism = 100)
        {
            volumeInformation = volumeInformation ?? throw new ArgumentNullException(nameof(volumeInformation));

            // Allocate the array for reading the volume data.
            var result = new Volume3D <short>(
                (int)volumeInformation.Width,
                (int)volumeInformation.Height,
                (int)volumeInformation.Depth,
                volumeInformation.VoxelWidthInMillimeters,
                volumeInformation.VoxelHeightInMillimeters,
                volumeInformation.VoxelDepthInMillimeters,
                volumeInformation.Origin,
                volumeInformation.Direction);

            Parallel.For(
                0,
                volumeInformation.Depth,
                new ParallelOptions()
            {
                MaxDegreeOfParallelism = (int)maxDegreeOfParallelism
            },
                i => WriteSlice(result, volumeInformation.GetSliceInformation((int)i), (uint)i));

            return(result);
        }
예제 #2
0
        /// <summary>
        /// Checks the slice spacing conformance based on the acceptance test.
        /// </summary>
        /// <param name="volumeInformation">The volume information.</param>
        /// <param name="acceptanceTest">The acceptance test.</param>
        /// <exception cref="ArgumentNullException">The volume information or acceptance test was null.</exception>
        /// <exception cref="ArgumentException">The acceptance test did not pass for a slice.</exception>
        private static void CheckSliceSpacingConformance(VolumeInformation volumeInformation, IVolumeGeometricAcceptanceTest acceptanceTest)
        {
            for (var i = 1; i < volumeInformation.Depth; i++)
            {
                var spacing = volumeInformation.GetSliceInformation(i).SlicePosition - volumeInformation.GetSliceInformation(i - 1).SlicePosition;

                if (!acceptanceTest.AcceptSliceSpacingError(volumeInformation.SopClass, spacing, volumeInformation.VoxelDepthInMillimeters))
                {
                    throw new ArgumentException($"The spacing between slice {i - 1} and {i} was inconsistent.");
                }
            }
        }
예제 #3
0
        /// <summary>
        /// Validates the provided volume information in accordance with the provided volume geometric acceptance test and
        /// that every slice in the volume is valid.
        /// This will check:
        ///     1. Validates each slice using the validate slice information method (and will use the supported transfer syntaxes if provided).
        ///     2. Grid conformance of the volume.
        ///     3. Slice spacing conformance for each slice.
        ///     4. Executes the propose method of the acceptance test.
        /// </summary>
        /// <param name="volumeInformation">The volume information.</param>
        /// <param name="volumeGeometricAcceptanceTest">The volume geometric acceptance test.</param>
        /// <param name="supportedTransferSyntaxes">The supported transfer syntaxes or null if we do not want to check against this.</param>
        /// <exception cref="ArgumentNullException">The volume information or acceptance test is null.</exception>
        /// <exception cref="ArgumentException">An acceptance test did not pass.</exception>
        public static void ValidateVolumeInformation(
            VolumeInformation volumeInformation,
            IVolumeGeometricAcceptanceTest volumeGeometricAcceptanceTest,
            IReadOnlyCollection <DicomTransferSyntax> supportedTransferSyntaxes = null)
        {
            volumeInformation             = volumeInformation ?? throw new ArgumentNullException(nameof(volumeInformation));
            volumeGeometricAcceptanceTest = volumeGeometricAcceptanceTest ?? throw new ArgumentNullException(nameof(volumeGeometricAcceptanceTest));

            // 1. Validate each slice.
            for (var i = 0; i < volumeInformation.Depth; i++)
            {
                // Validate the DICOM tags of each slice.
                ValidateSliceInformation(volumeInformation.GetSliceInformation(i), supportedTransferSyntaxes);

                if (i > 0)
                {
                    // Validate the slice information is consistent across slices using the first slice as a reference.
                    ValidateSliceInformation(volumeInformation.GetSliceInformation(i), volumeInformation.GetSliceInformation(0));
                }
            }

            // 2. + 3. Check the slice and grid conformance of the volume information.
            CheckGridConformance(volumeInformation, volumeGeometricAcceptanceTest);
            CheckSliceSpacingConformance(volumeInformation, volumeGeometricAcceptanceTest);

            // 4. Run acceptance testing.
            var acceptanceErrorMessage = string.Empty;

            if (!volumeGeometricAcceptanceTest.Propose(
                    volumeInformation.SopClass,
                    volumeInformation.Origin,
                    volumeInformation.Direction,
                    new Point3D(volumeInformation.VoxelWidthInMillimeters, volumeInformation.VoxelHeightInMillimeters, volumeInformation.Depth),
                    out acceptanceErrorMessage))
            {
                throw new ArgumentException(acceptanceErrorMessage, nameof(volumeInformation));
            }
        }
예제 #4
0
        /// <summary>
        /// Checks the grid conformance of the provided volume information based on the provided geometric acceptance test.
        /// </summary>
        /// <param name="volumeInformation">The volume information.</param>
        /// <param name="acceptanceTest">The acceptance test.</param>
        /// <exception cref="ArgumentNullException">The volume information or acceptance test was null.</exception>
        /// <exception cref="ArgumentException">The series did not conform to a regular grid.</exception>
        private static void CheckGridConformance(VolumeInformation volumeInformation, IVolumeGeometricAcceptanceTest acceptanceTest)
        {
            volumeInformation = volumeInformation ?? throw new ArgumentNullException(nameof(volumeInformation));
            acceptanceTest    = acceptanceTest ?? throw new ArgumentNullException(nameof(acceptanceTest));

            var scales = Matrix3.Diag(
                volumeInformation.VoxelWidthInMillimeters,
                volumeInformation.VoxelHeightInMillimeters,
                volumeInformation.VoxelDepthInMillimeters);

            if (volumeInformation.Depth != volumeInformation.Depth)
            {
                throw new ArgumentException("Mismatch between depth and number of slices.", nameof(volumeInformation));
            }

            for (int z = 0; z < volumeInformation.Depth; z++)
            {
                var sliceInformation = volumeInformation.GetSliceInformation(z);
                var sliceScales      = Matrix3.Diag(sliceInformation.VoxelWidthInMillimeters, sliceInformation.VoxelHeightInMillimeters, 0);
                var sliceOrientation = Matrix3.FromColumns(sliceInformation.Direction.Column(0), sliceInformation.Direction.Column(1), new Point3D(0, 0, 0));

                // Check corners of each slice only
                for (uint y = 0; y < sliceInformation.Height; y += sliceInformation.Height - 1)
                {
                    for (uint x = 0; x < sliceInformation.Width; x += sliceInformation.Width - 1)
                    {
                        var point = new Point3D(x, y, z);
                        var patientCoordViaSliceFrame = sliceOrientation * sliceScales * point + sliceInformation.Origin;
                        var patientCoordViaGridFrame  = volumeInformation.Direction * scales * point + volumeInformation.Origin;

                        if (!acceptanceTest.AcceptPositionError(volumeInformation.SopClass, patientCoordViaSliceFrame, patientCoordViaGridFrame))
                        {
                            throw new ArgumentException("The series did not conform to a regular grid.", nameof(volumeInformation));
                        }
                    }
                }
            }
        }