Exemplo n.º 1
0
			public void Dispose()
			{
				if (_volume != null)
				{
					_volume.OnReferenceDisposed();
					_volume = null;
				}
			}
Exemplo n.º 2
0
		public MprStandardSliceSet(Volume volume, IVolumeSlicerParams slicerParams) : base(volume)
		{
			Platform.CheckForNullReference(slicerParams, "slicerParams");
			_slicerParams = slicerParams;

			base.Description = slicerParams.Description;
			this.Reslice();
		}
Exemplo n.º 3
0
        //TODO (CR Sept 2010): same comment as with the ProgressGraphic stuff; the API is unclear
        //as to what it is doing (return value) because it's trying to account for the async loading.

        /// <summary>
        /// Attempts to start loading the overlay data asynchronously, if not already loaded.
        /// </summary>
        /// <param name="progress">A value between 0 and 1 indicating the progress of the asynchronous loading operation.</param>
        /// <param name="message">A string message detailing the progress of the asynchronous loading operation.</param>
        /// <returns></returns>
        public bool BeginLoad(out float progress, out string message)
        {
            // update the last access time
            _largeObjectData.UpdateLastAccessTime();

            // if the data is already available without blocking, return success immediately

            //TODO (CR Sept 2010): because unloading the volume involves disposing it, if every operation that uses it
            //isn't in the same lock (e.g. lock(_syncVolumeDataLock)) you could be getting a disposed/null volume here.
            VolumeData volume = _volume;

            if (volume != null)
            {
                message  = SR.MessageFusionComplete;
                progress = 1f;
                return(true);
            }

            lock (_syncLoaderLock)
            {
                message  = SR.MessageFusionInProgress;
                progress = 0;
                if (_volumeLoaderTask == null)
                {
                    // if the data is available now, return success
                    volume = _volume;
                    if (volume != null)
                    {
                        message  = SR.MessageFusionComplete;
                        progress = 1f;
                        return(true);
                    }

                    _volumeLoaderTask = new BackgroundTask(c => this.LoadVolume(c), false, null)
                    {
                        ThreadUICulture = Application.CurrentUICulture
                    };
                    _volumeLoaderTask.Run();
                    _volumeLoaderTask.Terminated += OnVolumeLoaderTaskTerminated;
                }
                else
                {
                    // TODO (CR Apr 2013): See comment in OnVolumeLoaderTaskTerminated; if the task were not created
                    // on the UI thread, _volumeLoaderTask could be set to null before hitting this instruction.
                    if (_volumeLoaderTask.LastBackgroundTaskProgress != null)
                    {
                        message  = _volumeLoaderTask.LastBackgroundTaskProgress.Progress.Message;
                        progress = _volumeLoaderTask.LastBackgroundTaskProgress.Progress.Percent / 100f;
                    }
                }
            }
            return(false);
        }
Exemplo n.º 4
0
		protected MprSliceSet(Volume volume)
		{
			Platform.CheckForNullReference(volume, "volume");
			_volume = volume.CreateTransientReference();

			_sliceSops = new ObservableDisposableList<MprSliceSop>();
			_sliceSops.EnableEvents = true;
			_sliceSops.ItemAdded += OnItemAdded;
			_sliceSops.ItemChanged += OnItemChanged;
			_sliceSops.ItemChanging += OnItemChanging;
			_sliceSops.ItemRemoved += OnItemRemoved;
		}
Exemplo n.º 5
0
		private static IEnumerable<IMprSliceSet> CreateDefaultSliceSets(Volume volume)
		{
			// The default slice sets consist of a fixed view of the original image plane,
			// and three mutable slice sets showing the other two planes perpendicular to the original
			// plus one oblique slice set halfway in between these two perpendicular planes.
			if (volume != null)
			{
				yield return MprStaticSliceSet.CreateIdentitySliceSet(volume);
				yield return new MprStandardSliceSet(volume, VolumeSlicerParams.OrthogonalX);
				yield return new MprStandardSliceSet(volume, new VolumeSlicerParams(90, 0, 270));
				yield return new MprStandardSliceSet(volume, new VolumeSlicerParams(90, 0, 315));
			}
		}
Exemplo n.º 6
0
        private VolumeData LoadVolume(IBackgroundTaskContext context)
        {
            // TODO (CR Apr 2013): Ideally, loading and unloading could be done with minimal locking; this way,
            // Unload actually has to wait for Load to finish and vice versa. I think a quicker way would be to
            // have a _loading field - have this method set it inside the lock, then proceed to do the load,
            // then set the _volume field when done. Have Unload check _loading and just return, otherwise set _volume to null.
            // Basically, you don't need to lock the entire load operation - you only need to guarantee that multiple loads
            // can't occur at once, and that Unload actually unloads it.

            // wait for synchronized access
            lock (_syncVolumeDataLock)
            {
                _largeObjectData.Lock();
                try
                {
                    // if the data is now available, return it immediately
                    // (i.e. we were blocked because we were already reading the data)
                    if (_volume != null)
                    {
                        return(_volume);
                    }

                    // load the volume data
                    if (context == null)
                    {
                        _volume = VolumeData.Create(_frames);
                    }
                    else
                    {
                        _volume = VolumeData.Create(_frames, (n, count) => context.ReportProgress(new BackgroundTaskProgress(n, count, SR.MessageFusionInProgress)));
                    }

                    // update our stats
                    _largeObjectData.BytesHeldCount   = 2 * _volume.SizeInVoxels;
                    _largeObjectData.LargeObjectCount = 1;
                    _largeObjectData.UpdateLastAccessTime();

                    // regenerating the volume data takes a few seconds
                    _largeObjectData.RegenerationCost = LargeObjectContainerData.PresetComputedData;

                    // register with memory manager
                    MemoryManager.Add(this);

                    return(_volume);
                }
                finally
                {
                    _largeObjectData.Unlock();
                }
            }
        }
Exemplo n.º 7
0
		public VolumeSliceSopDataSource(Volume volume, IVolumeSlicerParams slicerParams, IList<Vector3D> throughPoints)
		{
			Platform.CheckForNullReference(throughPoints, "throughPoints");
			Platform.CheckTrue(throughPoints.Count > 0, "At least one through point must be specified.");

			_volumeReference = volume.CreateTransientReference();
			_slicerParams = slicerParams;
			_resliceMatrix = new Matrix(slicerParams.SlicingPlaneRotation);
			_resliceMatrix[3, 0] = throughPoints[0].X;
			_resliceMatrix[3, 1] = throughPoints[0].Y;
			_resliceMatrix[3, 2] = throughPoints[0].Z;
			_throughPoints = new List<Vector3D>(throughPoints).AsReadOnly();

			_instanceDataSet = new DicomAttributeCollection();

			// JY: ideally, each slicing plane is represented by a single multiframe SOP where the individual slices are the frames.
			// We need to support multi-valued Slice Location in the base viewer first.
			// When that is implemented, the SOPs should be created on the first frame of the slicing (i.e. one of the end slices)
			// and the Slice Location Vector will simply store the slice locations relative to that defined in these attributes.
			// Also, the rows and columns will have to be computed to be the MAX possible size (all frames must have same size)

			// assign Rows and Columns to reflect actual output size
			Size frameSize = GetSliceExtent(volume, slicerParams);
			_instanceDataSet[DicomTags.Columns].SetInt32(0, frameSize.Width);
			_instanceDataSet[DicomTags.Rows].SetInt32(0, frameSize.Height);

			// assign Image Orientation (Patient)
			Matrix resliceAxesPatientOrientation = _volumeReference.Volume.RotateToPatientOrientation(_resliceMatrix);
			_instanceDataSet[DicomTags.ImageOrientationPatient].SetFloat32(0, resliceAxesPatientOrientation[0, 0]);
			_instanceDataSet[DicomTags.ImageOrientationPatient].SetFloat32(1, resliceAxesPatientOrientation[0, 1]);
			_instanceDataSet[DicomTags.ImageOrientationPatient].SetFloat32(2, resliceAxesPatientOrientation[0, 2]);
			_instanceDataSet[DicomTags.ImageOrientationPatient].SetFloat32(3, resliceAxesPatientOrientation[1, 0]);
			_instanceDataSet[DicomTags.ImageOrientationPatient].SetFloat32(4, resliceAxesPatientOrientation[1, 1]);
			_instanceDataSet[DicomTags.ImageOrientationPatient].SetFloat32(5, resliceAxesPatientOrientation[1, 2]);

			// assign Image Position (Patient)
			Vector3D topLeftOfSlicePatient = GetTopLeftOfSlicePatient(frameSize, throughPoints[0], volume, slicerParams);
			_instanceDataSet[DicomTags.ImagePositionPatient].SetFloat32(0, topLeftOfSlicePatient.X);
			_instanceDataSet[DicomTags.ImagePositionPatient].SetFloat32(1, topLeftOfSlicePatient.Y);
			_instanceDataSet[DicomTags.ImagePositionPatient].SetFloat32(2, topLeftOfSlicePatient.Z);

			// assign Number of Frames
			_instanceDataSet[DicomTags.NumberOfFrames].SetInt32(0, throughPoints.Count);

			// assign a new SOP instance UID
			_instanceDataSet[DicomTags.SopInstanceUid].SetString(0, DicomUid.GenerateUid().UID);
		}
Exemplo n.º 8
0
        private void UnloadVolume()
        {
            // wait for synchronized access
            lock (_syncVolumeDataLock)
            {
                // dump our data
                if (_volume != null)
                {
                    _volume.Dispose();
                    _volume = null;
                }

                // update our stats
                _largeObjectData.BytesHeldCount   = 0;
                _largeObjectData.LargeObjectCount = 0;

                // unregister with memory manager
                MemoryManager.Remove(this);
            }

            this.OnUnloaded();
        }
Exemplo n.º 9
0
        private VolumeData LoadVolume(IBackgroundTaskContext context)
        {
            // wait for synchronized access
            lock (_syncVolumeDataLock)
            {
                // if the data is now available, return it immediately
                // (i.e. we were blocked because we were already reading the data)
                if (_volume != null)
                {
                    return(_volume);
                }

                // load the volume data
                if (context == null)
                {
                    _volume = VolumeData.Create(_frames);
                }
                else
                {
                    _volume = VolumeData.Create(_frames, (n, count) => context.ReportProgress(new BackgroundTaskProgress(n, count, SR.MessageFusionInProgress)));
                }

                // update our stats
                _largeObjectData.BytesHeldCount   = 2 * _volume.SizeInVoxels;
                _largeObjectData.LargeObjectCount = 1;
                _largeObjectData.UpdateLastAccessTime();

                // regenerating the volume data is easy when the source frames are already in memory!
                _largeObjectData.RegenerationCost = RegenerationCost.Low;

                // register with memory manager
                MemoryManager.Add(this);

                return(_volume);
            }
        }
Exemplo n.º 10
0
		public MprVolume(Volume volume, IEnumerable<IMprSliceSet> sliceSets)
		{
			Platform.CheckForNullReference(volume, "volume");

			// MprVolume is the de jure owner of the Volume
			// Everything else (like the SOPs) just hold transient references
			_volume = volume;

			_sliceSets = new ObservableDisposableList<IMprSliceSet>();
			if (sliceSets != null)
			{
				foreach (IMprSliceSet sliceSet in sliceSets)
				{
					if (sliceSet is IInternalMprSliceSet)
						((IInternalMprSliceSet) sliceSet).Parent = this;
					_sliceSets.Add(sliceSet);
				}
			}
			_sliceSets.EnableEvents = true;
			_sliceSets.ItemAdded += OnItemAdded;
			_sliceSets.ItemChanged += OnItemAdded;
			_sliceSets.ItemChanging += OnItemRemoved;
			_sliceSets.ItemRemoved += OnItemRemoved;
		}
Exemplo n.º 11
0
		private void UnloadVolume()
		{
			// wait for synchronized access
			lock (_syncVolumeDataLock)
			{
				// dump our data
				if (_volume != null)
				{
					_volume.Dispose();
					_volume = null;
				}

				// update our stats
				_largeObjectData.BytesHeldCount = 0;
				_largeObjectData.LargeObjectCount = 0;

				// unregister with memory manager
				MemoryManager.Remove(this);
			}

			this.OnUnloaded();
		}
Exemplo n.º 12
0
		private VolumeData LoadVolume(IBackgroundTaskContext context)
		{
			// wait for synchronized access
			lock (_syncVolumeDataLock)
			{
				// if the data is now available, return it immediately
				// (i.e. we were blocked because we were already reading the data)
				if (_volume != null)
					return _volume;

				// load the volume data
				if (context == null)
					_volume = VolumeData.Create(_frames);
				else
					_volume = VolumeData.Create(_frames, (n, count) => context.ReportProgress(new BackgroundTaskProgress(n, count, SR.MessageFusionInProgress)));

				// update our stats
				_largeObjectData.BytesHeldCount = 2*_volume.SizeInVoxels;
				_largeObjectData.LargeObjectCount = 1;
				_largeObjectData.UpdateLastAccessTime();

				// regenerating the volume data is easy when the source frames are already in memory!
				_largeObjectData.RegenerationCost = RegenerationCost.Low;

				// register with memory manager
				MemoryManager.Add(this);

				return _volume;
			}
		}
Exemplo n.º 13
0
		public VolumeSlicer(Volume volume, IVolumeSlicerParams slicerParams)
		{
			_volume = volume.CreateTransientReference();
			_slicerParams = slicerParams;
		}
Exemplo n.º 14
0
		public VolumeSlicer(Volume vol, IVolumeSlicerParams slicerParams, string seriesInstanceUid)
		{
			_volume = vol.CreateTransientReference();
			_slicerParams = slicerParams;
			_seriesInstanceUid = seriesInstanceUid;
		}
Exemplo n.º 15
0
			/// <summary>
			/// Creates and populates a <see cref="Volume"/> from the builder's source frames.
			/// </summary>
			public Volume Build()
			{
				PrepareFrames(_frames); // this also sorts the frames into order by slice location

				// Construct a model SOP data source based on the first frame's DICOM header
				var sopDataSourcePrototype = VolumeSopDataSourcePrototype.Create(_frames[0].Sop.DataSource, 16, 16, false);

				// compute normalized modality LUT
				double normalizedSlope, normalizedIntercept;
				ComputeNormalizedModalityLut(_frames, out normalizedSlope, out normalizedIntercept);
				sopDataSourcePrototype[DicomTags.RescaleSlope].SetFloat64(0, normalizedSlope);
				sopDataSourcePrototype[DicomTags.RescaleIntercept].SetFloat64(0, normalizedIntercept);
				sopDataSourcePrototype[DicomTags.RescaleType] = _frames[0].Sop.DataSource[DicomTags.RescaleType].Copy();
				sopDataSourcePrototype[DicomTags.Units] = _frames[0].Sop.DataSource[DicomTags.Units].Copy(); // PET series use this attribute to designate rescale units

				// compute normalized VOI windows
				VoiWindow.SetWindows(ComputeNormalizedVoiWindows(_frames, normalizedSlope, normalizedIntercept), sopDataSourcePrototype);

				// compute the volume padding value
				var pixelPaddingValue = ComputePixelPaddingValue(_frames, normalizedSlope, normalizedIntercept);

				int minVolumeValue, maxVolumeValue;
				var volumeArray = BuildVolumeArray(pixelPaddingValue, normalizedSlope, normalizedIntercept, out minVolumeValue, out maxVolumeValue);
				var volume = new Volume(null, volumeArray, VolumeSize, VoxelSpacing, ImagePositionPatient, ImageOrientationPatient, sopDataSourcePrototype, pixelPaddingValue, _frames[0].Frame.SeriesInstanceUid, minVolumeValue, maxVolumeValue);
				return volume;
			}
Exemplo n.º 16
0
		private static IEnumerable<IMprSliceSet> CreateStandardSliceSets(Volume volume, IEnumerable<IVolumeSlicerParams> slicerParams)
		{
			if (volume != null && slicerParams != null)
			{
				foreach (IVolumeSlicerParams slicerParam in slicerParams)
					yield return new MprStandardSliceSet(volume, slicerParam);
			}
		}
Exemplo n.º 17
0
		public static MprStaticSliceSet CreateIdentitySliceSet(Volume volume)
		{
			return new MprStaticSliceSet(volume, VolumeSlicerParams.Identity);
		}
Exemplo n.º 18
0
		public MprVolume(Volume volume, IEnumerable<IVolumeSlicerParams> slicerParams) : this(volume, CreateStandardSliceSets(volume, slicerParams)) {}
Exemplo n.º 19
0
		// Derived from either a specified extent in millimeters or from the volume dimensions (default)
		private static Size GetSliceExtent(Volume volume, IVolumeSlicerParams slicerParams)
		{
			var effectiveSpacing = GetEffectiveSpacing(volume);
			var longOutputDimension = volume.LongAxisMagnitude/effectiveSpacing;
			var shortOutputDimenstion = volume.ShortAxisMagnitude/effectiveSpacing;
			var diagonalDimension = (int) Math.Sqrt(longOutputDimension*longOutputDimension + shortOutputDimenstion*shortOutputDimenstion);

			var columns = diagonalDimension;
			if (!FloatComparer.AreEqual(slicerParams.SliceExtentXMillimeters, 0f))
				columns = (int) (slicerParams.SliceExtentXMillimeters/effectiveSpacing + 0.5f);

			var rows = diagonalDimension;
			if (!FloatComparer.AreEqual(slicerParams.SliceExtentYMillimeters, 0f))
				rows = (int) (slicerParams.SliceExtentYMillimeters/effectiveSpacing + 0.5f);

			return new Size(columns, rows);
		}
		public VolumeSliceSopDataSource(Volume volume, IVolumeSlicerParams slicerParams, IList<Vector3D> throughPoints)
			: this(volume.CreateTransientReference(), slicerParams, throughPoints) {}
Exemplo n.º 21
0
		/// <summary>
		/// The effective spacing defines output spacing for slices generated by the VolumeSlicer.
		/// </summary>
		private static float GetEffectiveSpacing(Volume volume)
		{
			// Because we supply the real spacing to the VTK reslicer, the slices are interpolated
			//	as if the volume were isotropic. This results in an effective spacing that is the
			//	minimum spacing for the volume.
			return volume.MinimumSpacing;
		}
Exemplo n.º 22
0
		//TODO (cr Oct 2009): can be factored out into Slice class.

		// VTK treats the reslice point as the center of the output image. Given the plane orientation
		//	and size of the output image, we can derive the top left of the output image in patient space
		private static Vector3D GetTopLeftOfSlicePatient(Size frameSize, Vector3D throughPoint, Volume volume, IVolumeSlicerParams slicerParams)
		{
			// This is the center of the output image
			PointF centerImageCoord = new PointF(frameSize.Width/2f, frameSize.Height/2f);

			// These offsets define the x and y vector magnitudes to arrive at our point
			float effectiveSpacing = GetEffectiveSpacing(volume);
			float offsetX = centerImageCoord.X*effectiveSpacing;
			float offsetY = centerImageCoord.Y*effectiveSpacing;

			// To determine top left of slice in volume, subtract offset vectors along x and y
			//
			// Our reslice place x and y vectors
			Matrix resliceAxes = slicerParams.SlicingPlaneRotation;
			Vector3D xVec = new Vector3D(resliceAxes[0, 0], resliceAxes[0, 1], resliceAxes[0, 2]);
			Vector3D yVec = new Vector3D(resliceAxes[1, 0], resliceAxes[1, 1], resliceAxes[1, 2]);
			// Offset along x and y from reslicePoint
			Vector3D topLeftOfSliceVolume = throughPoint - (offsetX*xVec + offsetY*yVec);

			// Convert volume point to patient space
			return volume.ConvertToPatient(topLeftOfSliceVolume);
		}
Exemplo n.º 23
0
		public VolumeSlice(Volume volume, IVolumeSlicerParams slicerParams, Vector3D throughPoint)
			: this(volume.CreateTransientReference(), slicerParams, throughPoint) {}
Exemplo n.º 24
0
			public VolumeReference(Volume volume)
			{
				_volume = volume;
				_volume.OnReferenceCreated();
			}
Exemplo n.º 25
0
		/// <summary>
		/// Allows specification of the slice plane, through point, and extent via two points in patient space
		/// </summary>
		public static VolumeSlicerParams Create(Volume volume, Vector3D sourceOrientationColumnPatient, Vector3D sourceOrientationRowPatient,
		                                               Vector3D startPointPatient, Vector3D endPointPatient)
		{
			Vector3D sourceOrientationNormalPatient = sourceOrientationColumnPatient.Cross(sourceOrientationRowPatient);
			Vector3D normalLinePatient = (endPointPatient - startPointPatient).Normalize();
			Vector3D normalPerpLinePatient = sourceOrientationNormalPatient.Cross(normalLinePatient);

			Vector3D slicePlanePatientX = normalLinePatient;
			Vector3D slicePlanePatientY = sourceOrientationNormalPatient;
			Vector3D slicePlanePatientZ = normalPerpLinePatient;

			Matrix slicePlanePatientOrientation = Math3D.OrientationMatrixFromVectors(slicePlanePatientX, slicePlanePatientY, slicePlanePatientZ);

			Matrix _resliceAxes = volume.RotateToVolumeOrientation(slicePlanePatientOrientation);
			Vector3D lineMiddlePointPatient = new Vector3D(
				(startPointPatient.X + endPointPatient.X)/2,
				(startPointPatient.Y + endPointPatient.Y)/2,
				(startPointPatient.Z + endPointPatient.Z)/2);

			VolumeSlicerParams slicerParams = new VolumeSlicerParams(_resliceAxes);

			slicerParams.SliceThroughPointPatient = new Vector3D(lineMiddlePointPatient);
			slicerParams.SliceExtentXMillimeters = (endPointPatient - startPointPatient).Magnitude;

			return slicerParams;
		}
Exemplo n.º 26
0
		// Derived frome either a specified extent in millimeters or from the volume dimensions (default)
		private static Size GetSliceExtent(Volume volume, IVolumeSlicerParams slicerParams)
		{
			int rows, columns;

			float effectiveSpacing = GetEffectiveSpacing(volume);
			float longOutputDimension = volume.LongAxisMagnitude/effectiveSpacing;
			float shortOutputDimenstion = volume.ShortAxisMagnitude/effectiveSpacing;
			rows = columns = (int) Math.Sqrt(longOutputDimension*longOutputDimension + shortOutputDimenstion*shortOutputDimenstion);

			if (slicerParams.SliceExtentXMillimeters != 0f)
				columns = (int) (slicerParams.SliceExtentXMillimeters/effectiveSpacing + 0.5f);

			if (slicerParams.SliceExtentYMillimeters != 0f)
				rows = (int) (slicerParams.SliceExtentYMillimeters/effectiveSpacing + 0.5f);

			return new Size(columns, rows);
		}
Exemplo n.º 27
0
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (_sliceSets != null)
				{
					_sliceSets.ItemAdded -= OnItemAdded;
					_sliceSets.ItemChanged -= OnItemAdded;
					_sliceSets.ItemChanging -= OnItemRemoved;
					_sliceSets.ItemRemoved -= OnItemRemoved;
					_sliceSets.Dispose();
					_sliceSets = null;
				}

				if (_volume != null)
				{
					_volume.Dispose();
					_volume = null;
				}
			}
		}
Exemplo n.º 28
0
		//TODO (cr Oct 2009): pass in a Slice, generated by slicer, then can just call GetPixelData() on the slice.
		public VolumeSliceSopDataSource(Volume volume, IVolumeSlicerParams slicerParams, Vector3D throughPoint)
			: this(volume, slicerParams, new Vector3D[] {throughPoint}) {}
Exemplo n.º 29
0
		public MprVolume(Volume volume) : this(volume, CreateDefaultSliceSets(volume)) {}
Exemplo n.º 30
0
		public MprViewerComponent(Volume volume) : this()
		{
			_volumes.Add(new MprVolume(volume));
		}
Exemplo n.º 31
0
		private VolumeData LoadVolume(IBackgroundTaskContext context)
		{
		    // TODO (CR Apr 2013): Ideally, loading and unloading could be done with minimal locking; this way,
            // Unload actually has to wait for Load to finish and vice versa. I think a quicker way would be to
            // have a _loading field - have this method set it inside the lock, then proceed to do the load,
            // then set the _volume field when done. Have Unload check _loading and just return, otherwise set _volume to null.
            // Basically, you don't need to lock the entire load operation - you only need to guarantee that multiple loads
            // can't occur at once, and that Unload actually unloads it.

			// wait for synchronized access
			lock (_syncVolumeDataLock)
			{
				_largeObjectData.Lock();
				try
				{
					// if the data is now available, return it immediately
					// (i.e. we were blocked because we were already reading the data)
					if (_volume != null)
						return _volume;

					// load the volume data
					if (context == null)
						_volume = VolumeData.Create(_frames);
					else
						_volume = VolumeData.Create(_frames, (n, count) => context.ReportProgress(new BackgroundTaskProgress(n, count, SR.MessageFusionInProgress)));

					// update our stats
					_largeObjectData.BytesHeldCount = 2*_volume.SizeInVoxels;
					_largeObjectData.LargeObjectCount = 1;
					_largeObjectData.UpdateLastAccessTime();

					// regenerating the volume data takes a few seconds
					_largeObjectData.RegenerationCost = LargeObjectContainerData.PresetComputedData;

					// register with memory manager
					MemoryManager.Add(this);

					return _volume;
				}
				finally
				{
					_largeObjectData.Unlock();
				}
			}
		}