public void Refresh()
            {
                var clock = new CodeClock();
                clock.Start();

                var currentProcess = Process.GetCurrentProcess();
                currentProcess.Refresh();

                ProcessVirtualMemoryBytes = currentProcess.VirtualMemorySize64;
                ProcessPrivateBytes = currentProcess.PrivateMemorySize64;
                ProcessWorkingSetBytes = currentProcess.WorkingSet64;
                GCTotalBytesAllocated = GC.GetTotalMemory(false);
                SystemFreeMemoryBytes = SystemResources.GetAvailableMemory(SizeUnits.Bytes);

                HighWaterMarkBytes = MemoryManagementSettings.Default.HighWatermarkMegaBytes*OneMegabyte;
                LowWaterMarkBytes = MemoryManagementSettings.Default.LowWatermarkMegaBytes*OneMegabyte;
                HeldMemoryToCollectPercent = MemoryManagementSettings.Default.HeldMemoryToCollectPercent/100.0;

                x64MinimumFreeSystemMemoryBytes = MemoryManagementSettings.Default.x64MinimumFreeSystemMemoryMegabytes*OneMegabyte;
                x64MaxMemoryUsagePercent = MemoryManagementSettings.Default.x64MaxMemoryUsagePercent/100.0;
                x64MaxMemoryToCollectBytes = MemoryManagementSettings.Default.x64MaxMemoryToCollectMegabytes*OneMegabyte;

                MemoryManagerLargeObjectBytesCount = MemoryManager.LargeObjectBytesCount;

                clock.Stop();

                PerformanceReportBroker.PublishReport("Memory", "UpdateMemoryInfo", clock.Seconds);
            }
Beispiel #2
0
		public static PaletteColorMap Create(IDicomAttributeProvider dataSource)
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			PaletteColorLut paletteColorLut = PaletteColorLut.Create(dataSource);

			clock.Stop();
			PerformanceReportBroker.PublishReport("PaletteColorMap", "Create(IDicomAttributeProvider)", clock.Seconds);

			return new PaletteColorMap(paletteColorLut);
		}
Beispiel #3
0
		public static void Render(
			ImageGraphic imageGraphic,
			IntPtr pDstPixelData,
			int dstWidth,
			int dstBytesPerPixel,
			Rectangle clientRectangle)
		{
			if (clientRectangle.Width <= 0 || clientRectangle.Height <= 0)
				return;

			if (imageGraphic.SizeInBytes != imageGraphic.PixelData.Raw.Length)
				throw new InvalidOperationException(String.Format(SR.ExceptionIncorrectPixelDataSize, imageGraphic.SizeInBytes, imageGraphic.PixelData.Raw.Length));

#if DEBUG
			CodeClock clock = new CodeClock();
			clock.Start();
#endif
			RectangleF srcViewableRectangle;
			Rectangle dstViewableRectangle;

			CalculateVisibleRectangles(imageGraphic, clientRectangle, out dstViewableRectangle, out srcViewableRectangle);

		    var grayGraphic = imageGraphic as GrayscaleImageGraphic;
		    ColorImageGraphic colorGraphic;
            if (grayGraphic != null)
			{
				RenderGrayscale(
                    grayGraphic,
					srcViewableRectangle,
					dstViewableRectangle,
					pDstPixelData,
					dstWidth,
					dstBytesPerPixel);
			}
            else if (null != (colorGraphic = imageGraphic as ColorImageGraphic))
			{
				RenderColor(
                    colorGraphic,
					srcViewableRectangle,
					dstViewableRectangle,
					pDstPixelData,
					dstWidth,
					dstBytesPerPixel);
			}
			else
			{
				throw new Exception("Unknown ImageGraphic.");
			}
#if DEBUG
			clock.Stop();
			PerformanceReportBroker.PublishReport("ImageRenderer", "Render", clock.Seconds);
#endif
		}
Beispiel #4
0
		/// <summary>
		/// Draws an <see cref="ImageGraphic"/>.
		/// </summary>
		protected override void DrawImageGraphic(ImageGraphic imageGraphic)
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			const int bytesPerPixel = 4;

			Surface.ImageBuffer.Graphics.Clear(Color.FromArgb(0x0, 0xFF, 0xFF, 0xFF));

			BitmapData bitmapData = Surface.ImageBuffer.Bitmap.LockBits(
				new Rectangle(0, 0, Surface.ImageBuffer.Bitmap.Width, Surface.ImageBuffer.Bitmap.Height),
				ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

			try
			{
				ImageRenderer.Render(imageGraphic, bitmapData.Scan0, bitmapData.Width, bytesPerPixel, Surface.ClientRectangle);
			}
			finally
			{
				Surface.ImageBuffer.Bitmap.UnlockBits(bitmapData);
			}

			Surface.FinalBuffer.RenderImage(Surface.ImageBuffer);

			clock.Stop();
			PerformanceReportBroker.PublishReport("GDIRenderer", "DrawImageGraphic", clock.Seconds);
		}
Beispiel #5
0
		/// <summary>
		/// Traverses and renders the scene graph.  
		/// </summary>
		protected override void Render()
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			Surface.FinalBuffer.Graphics.Clear(Color.Black);
			base.Render();

			clock.Stop();
			PerformanceReportBroker.PublishReport("GDIRenderer", "Render", clock.Seconds);
		}
		/// <summary>
		/// Converts colour pixel data to ARGB.
		/// </summary>
		protected static byte[] ToArgb(IDicomAttributeProvider dicomAttributeProvider, byte[] pixelData, PhotometricInterpretation photometricInterpretation)
		{
#if DEBUG
			CodeClock clock = new CodeClock();
			clock.Start();
#endif
			int rows = dicomAttributeProvider[DicomTags.Rows].GetInt32(0, 0);
			int columns = dicomAttributeProvider[DicomTags.Columns].GetInt32(0, 0);
			int sizeInBytes = rows*columns*4;
			byte[] argbPixelData = MemoryManager.Allocate<byte>(sizeInBytes);

			// Convert palette colour images to ARGB so we don't get interpolation artifacts
			// when rendering.
			if (photometricInterpretation == PhotometricInterpretation.PaletteColor)
			{
				int bitsAllocated = dicomAttributeProvider[DicomTags.BitsAllocated].GetInt32(0, 0);
				int pixelRepresentation = dicomAttributeProvider[DicomTags.PixelRepresentation].GetInt32(0, 0);

				ColorSpaceConverter.ToArgb(
					bitsAllocated,
					pixelRepresentation != 0 ? true : false,
					pixelData,
					argbPixelData,
					PaletteColorMap.Create(dicomAttributeProvider));
			}
				// Convert RGB and YBR variants to ARGB
			else
			{
				int planarConfiguration = dicomAttributeProvider[DicomTags.PlanarConfiguration].GetInt32(0, 0);

				ColorSpaceConverter.ToArgb(
					photometricInterpretation,
					planarConfiguration,
					pixelData,
					argbPixelData);
			}

#if DEBUG
			clock.Stop();
			PerformanceReportBroker.PublishReport("DicomMessageSopDataSource", "ToArgb", clock.Seconds);
#endif
			return argbPixelData;
		}
			/// <summary>
			/// Called by the base class to create a new byte buffer containing normalized pixel data
			/// for this frame (8 or 16-bit grayscale, or 32-bit ARGB).
			/// </summary>
			/// <returns>A new byte buffer containing the normalized pixel data.</returns>
			protected override byte[] CreateNormalizedPixelData()
			{
				DicomMessageBase message = Parent.SourceMessage;
#if DEBUG
				CodeClock clock = new CodeClock();
				clock.Start();
#endif
				PhotometricInterpretation photometricInterpretation;
				byte[] rawPixelData = null;

				if (!message.TransferSyntax.Encapsulated)
				{
					DicomUncompressedPixelData pixelData = new DicomUncompressedPixelData(message);
					// DICOM library uses zero-based frame numbers
					MemoryManager.Execute(delegate { rawPixelData = pixelData.GetFrame(_frameIndex); });

					ExtractOverlayFrames(rawPixelData, pixelData.BitsAllocated);

					photometricInterpretation = PhotometricInterpretation.FromCodeString(message.DataSet[DicomTags.PhotometricInterpretation]);
				}
				else if (DicomCodecRegistry.GetCodec(message.TransferSyntax) != null)
				{
					DicomCompressedPixelData pixelData = new DicomCompressedPixelData(message);
					string pi = null;

					MemoryManager.Execute(delegate { rawPixelData = pixelData.GetFrame(_frameIndex, out pi); });

					photometricInterpretation = PhotometricInterpretation.FromCodeString(pi);
				}
				else
					throw new DicomCodecException("Unsupported transfer syntax");

				if (photometricInterpretation.IsColor)
					rawPixelData = ToArgb(message.DataSet, rawPixelData, photometricInterpretation);
				else
					NormalizeGrayscalePixels(message.DataSet, rawPixelData);
#if DEBUG
				clock.Stop();
				PerformanceReportBroker.PublishReport("DicomMessageSopDataSource", "CreateFrameNormalizedPixelData", clock.Seconds);
#endif
				return rawPixelData;
			}
			private RetrievePixelDataResult TryClientRetrievePixelData(out Exception lastRetrieveException)
			{
				// retry parameters
			    const int maxRetryCount = 10;
			    const int retryTimeout = 1500;
				int retryDelay = 50;
				int retryCounter = 0;
                	
				StreamingClient client = new StreamingClient(this.BaseUrl);
				RetrievePixelDataResult result = null;
				lastRetrieveException = null;

				CodeClock timeoutClock = new CodeClock();
				timeoutClock.Start();

				while (true)
				{
					try
					{
						if (retryCounter > 0)
							Platform.Log(LogLevel.Info, "Retrying retrieve pixel data for Sop '{0}' (Attempt #{1})", this.SopInstanceUid, retryCounter);

						CodeClock statsClock = new CodeClock();
						statsClock.Start();

						result = client.RetrievePixelData(this.AETitle, this.StudyInstanceUid, this.SeriesInstanceUid, this.SopInstanceUid, this.FrameNumber - 1);

						statsClock.Stop();

						Platform.Log(LogLevel.Debug, "[Retrieve Info] Sop/Frame: {0}/{1}, Transfer Syntax: {2}, Bytes transferred: {3}, Elapsed (s): {4}, Retries: {5}",
						             this.SopInstanceUid, this.FrameNumber, this.TransferSyntaxUid,
						             result.MetaData.ContentLength, statsClock.Seconds, retryCounter);

						break;
					}
					catch (Exception ex)
					{
						lastRetrieveException = ex;

						timeoutClock.Stop();
                        if (timeoutClock.Seconds * 1000 >= retryTimeout || retryCounter>=maxRetryCount)
						{
							// log an alert that we are aborting (exception trace at debug level only)
							int elapsed = (int)(1000*timeoutClock.Seconds);
							Platform.Log(LogLevel.Warn, "Failed to retrieve pixel data for Sop '{0}'; Aborting after {1} attempts in {2} ms", this.SopInstanceUid, retryCounter, elapsed);
							Platform.Log(LogLevel.Debug, ex, "[Retrieve Fail-Abort] Sop/Frame: {0}/{1}, Retry Attempts: {2}, Elapsed: {3} ms", this.SopInstanceUid, this.FrameNumber - 1, retryCounter, elapsed);
							break;
						}
						timeoutClock.Start();

						retryCounter++;

						// log the retry (exception trace at debug level only)
						Platform.Log(LogLevel.Warn, "Failed to retrieve pixel data for Sop '{0}'; Retrying in {1} ms", this.SopInstanceUid, retryDelay);
						Platform.Log(LogLevel.Debug, ex, "[Retrieve Fail-Retry] Sop/Frame: {0}/{1}, Retry in: {2} ms", this.SopInstanceUid, this.FrameNumber - 1, retryDelay);
						MemoryManager.Collect(retryDelay);
						retryDelay *= 2;

                        if (retryDelay > MaxRetryDelay)
                            retryDelay = MaxRetryDelay; // cap it to avoid overflow, which will cause exception when calling MemoryManager.Collect()
					}
				}

				return result;
			}
Beispiel #9
0
		private static float ExecutePerformanceTest(PerformanceTestCallback callback)
		{
			var values = new[] {-100300.43245325f, -1, -float.Epsilon, 0, float.Epsilon, 1, 90952.343542f};
			var mod = values.Length;
			var permutations = (int) Math.Pow(mod, 8);

			var cc = new CodeClock();
			cc.Start();
			for (int n = 0; n < permutations; n++)
			{
				callback.Invoke(
					new PointF(values[(n)%mod], values[(n/mod/mod/mod/mod/mod/mod/mod)%mod]),
					new PointF(values[(n/mod/mod)%mod], values[(n/mod/mod/mod/mod/mod)%mod]),
					new PointF(values[(n/mod/mod/mod/mod)%mod], values[(n/mod/mod/mod)%mod]),
					new PointF(values[(n/mod/mod/mod/mod/mod/mod)%mod], values[(n/mod)%mod])
					);
			}
			cc.Stop();
			return cc.Seconds*1000000/permutations;
		}
Beispiel #10
0
		public RetrievePixelDataResult RetrievePixelData(string serverAE, string studyInstanceUID, string seriesInstanceUID, string sopInstanceUid, int frame)
        {
			try
			{
				CodeClock clock = new CodeClock();
				clock.Start();

				FrameStreamingResultMetaData result = new FrameStreamingResultMetaData();
				StringBuilder url = new StringBuilder();

				if (_baseUri.ToString().EndsWith("/"))
				{
					url.AppendFormat("{0}{1}", _baseUri, serverAE);
				}
				else
				{
					url.AppendFormat("{0}/{1}", _baseUri, serverAE);
				}

				url.AppendFormat("?requesttype=WADO&studyUID={0}&seriesUID={1}&objectUID={2}", studyInstanceUID, seriesInstanceUID, sopInstanceUid);
				url.AppendFormat("&frameNumber={0}", frame);
				url.AppendFormat("&contentType={0}", HttpUtility.HtmlEncode("application/clearcanvas"));

				result.Speed.Start();

				HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url.ToString());
				request.Accept = "application/dicom,application/clearcanvas,image/jpeg";
				request.Timeout = (int) TimeSpan.FromSeconds(StreamingSettings.Default.ClientTimeoutSeconds).TotalMilliseconds;
				request.KeepAlive = false;

				HttpWebResponse response = (HttpWebResponse)request.GetResponse();

				if (response.StatusCode != HttpStatusCode.OK)
				{
					throw new StreamingClientException(response.StatusCode, HttpUtility.HtmlDecode(response.StatusDescription));
				}

				Stream responseStream = response.GetResponseStream();
				BinaryReader reader = new BinaryReader(responseStream);
				byte[] buffer = reader.ReadBytes((int) response.ContentLength);
				reader.Close();
				responseStream.Close();
				response.Close();

				result.Speed.SetData(buffer.Length);
				result.Speed.End();

				result.ResponseMimeType = response.ContentType;
				result.Status = response.StatusCode;
				result.StatusDescription = response.StatusDescription;
				result.Uri = response.ResponseUri;
				result.ContentLength = buffer.Length;
				result.IsLast = (response.Headers["IsLast"] != null && bool.Parse(response.Headers["IsLast"]));

				clock.Stop();
				PerformanceReportBroker.PublishReport("Streaming", "RetrievePixelData", clock.Seconds);

				RetrievePixelDataResult pixelDataResult;
				if (response.Headers["Compressed"] != null && bool.Parse(response.Headers["Compressed"]))
					pixelDataResult = new RetrievePixelDataResult(CreateCompressedPixelData(response, buffer), result);
				else
					pixelDataResult = new RetrievePixelDataResult(buffer, result);

				return pixelDataResult;
			}
			catch (WebException ex)
			{
				if (ex.Status == WebExceptionStatus.ProtocolError && ex.Response is HttpWebResponse)
				{
					HttpWebResponse response = (HttpWebResponse) ex.Response;
					throw new StreamingClientException(response.StatusCode, HttpUtility.HtmlDecode(response.StatusDescription));
				}
				throw new StreamingClientException(StreamingClientExceptionType.Network, ex);
			}
		}
		/// <summary>
		/// Calculates the Minimum and Maximum pixel values from the pixel data efficiently, using unsafe code.
		/// </summary>
		/// <param name="minPixelValue">Returns the minimum pixel value.</param>
		/// <param name="maxPixelValue">Returns the maximum pixel value.</param>
		unsafe public void CalculateMinMaxPixelValue(out int minPixelValue, out int maxPixelValue)
		{
			byte[] pixelData = GetPixelData();

#if DEBUG
			CodeClock clock = new CodeClock();
			clock.Start();
#endif
			if (_isSigned)
			{
				if (_bitsAllocated == 8)
				{
					fixed (byte* ptr = pixelData)
					{
						byte* pixel = (byte*)ptr;

						byte signMask = (byte)(1 << (_bitsStored - 1));

						sbyte max, min;
						max = sbyte.MinValue;
						min = sbyte.MaxValue;

						for (int i = 0; i < _rows * _columns; ++i)
						{
							sbyte result;
							if (0 == ((*pixel) & signMask))
							{
								result = (sbyte)(*pixel);
							}
							else
							{
								byte inverted = (byte)(~(*pixel));
								// Need to mask out the bits greater above the high bit, since they're irrelevant
								byte mask = (byte)(byte.MaxValue >> (_bitsAllocated - _bitsStored));
								byte maskedInverted = (byte)(inverted & mask);
								result = (sbyte)(-(maskedInverted + 1));
							}

							if (result > max)
								max = result;
							else if (result < min)
								min = result;

							++pixel;
						}

						maxPixelValue = (int)max;
						minPixelValue = (int)min;
					}
				}
				else
				{
					fixed (byte* ptr = pixelData)
					{
						UInt16* pixel = (UInt16*)ptr;

						UInt16 signMask = (UInt16)(1 << (_bitsStored - 1));

						Int16 max, min;
						max = Int16.MinValue;
						min = Int16.MaxValue;

						for (int i = 0; i < _rows * _columns; ++i)
						{
							Int16 result;
							if (0 == ((*pixel) & signMask))
							{
								result = (Int16)(*pixel);
							}
							else
							{
								UInt16 inverted = (UInt16)(~(*pixel));
								// Need to mask out the bits greater above the high bit, since they're irrelevant
								UInt16 mask = (UInt16)(UInt16.MaxValue >> (_bitsAllocated - _bitsStored));
								UInt16 maskedInverted = (UInt16)(inverted & mask);
								result = (Int16)(-(maskedInverted + 1));
							}

							if (result > max)
								max = result;
							else if (result < min)
								min = result;

							++pixel;
						}

						maxPixelValue = (int)max;
						minPixelValue = (int)min;
					}
				}
			}
			else
			{
				if (_bitsAllocated == 8)
				{
					fixed (byte* ptr = pixelData)
					{
						byte* pixel = ptr;

						byte max, min;
						max = min = *pixel;

						for (int i = 1; i < _rows * _columns; ++i)
						{
							if (*pixel > max)
								max = *pixel;
							else if (*pixel < min)
								min = *pixel;

							++pixel;
						}

						maxPixelValue = (int)max;
						minPixelValue = (int)min;
					}
				}
				else
				{
					fixed (byte* ptr = pixelData)
					{
						UInt16* pixel = (UInt16*)ptr;

						UInt16 max, min;
						max = min = *pixel;

						for (int i = 1; i < _rows * _columns; ++i)
						{
							if (*pixel > max)
								max = *pixel;
							else if (*pixel < min)
								min = *pixel;

							++pixel;
						}

						maxPixelValue = (int)max;
						minPixelValue = (int)min;
					}
				}
			}

#if DEBUG
			clock.Stop();
			Trace.WriteLine(String.Format("Min/Max pixel value calculation took {0:F3} seconds (rows = {1}, columns = {2})", clock.Seconds, _rows, _columns));
#endif
		}
		private ImageViewerComponent LoadAndOpenStudies()
		{
			var codeClock = new CodeClock();
			codeClock.Start();

			var viewer = CreateViewer(LoadPriors);
			var desktopWindow = DesktopWindow ?? Application.ActiveDesktopWindow;

			try
			{
				viewer.LoadStudies(_studiesToOpen);
			}
            catch(InUseLoadStudyException)
            {
                if (!HandleStudyInUseError(viewer))
                {
                    viewer.Dispose();
                    return null;
                }
            }
            catch(LoadMultipleStudiesException ex)
            {
                // Note: although there may be other errors, we only need to handle exceptions caused by study being processed.
                // Other errors will (probably) happen again when all studies are reloaded and will be handled in HandleStudyInUseError.
                if (ex.InUseCount > 0)
                {
                    if (!HandleStudyInUseError(viewer))
                    {
                        viewer.Dispose();
                        return null;
                    }
                }
                else
                {
                    ExceptionHandler.Report(ex, SR.MessageFailedToOpenStudy, desktopWindow);
                }
            }
			catch (Exception e)
			{
				ExceptionHandler.Report(e, SR.MessageFailedToOpenStudy, desktopWindow);
			}

			if (!AnySopsLoaded(viewer) && !AllowEmptyViewer)
			{
				viewer.Dispose();
				return null;
			}

			var args = new LaunchImageViewerArgs(WindowBehaviour) {Title = Title};
			ImageViewerComponent.Launch(viewer, args);

			codeClock.Stop(); // note: the time will be skewed if the workstation prompts users for action
			Platform.Log(LogLevel.Debug, string.Format("TTFI: {0}", codeClock));

			return viewer;
		}
		public override void Collect(MemoryCollectionArgs collectionArgs)
		{
			_largeObjectEnumerator = collectionArgs.LargeObjectContainers.GetEnumerator();

			_regenerationCost = RegenerationCost.Low;

			//TODO (Time Review): Use Environment.TickCount?
			_collectionStartTime = DateTime.Now;
			_timeSinceLastCollection = _collectionStartTime - _lastCollectionTime;
			TimeSpan thirtySeconds = TimeSpan.FromSeconds(30);
			if (_timeSinceLastCollection < thirtySeconds)
			{
				Platform.Log(LogLevel.Debug, "Time since last collection is less than 30 seconds; adjusting to 30 seconds.");
				_timeSinceLastCollection = thirtySeconds;
			}

			_maxTimeSinceLastAccess = _timeSinceLastCollection;
			_maxTimeSinceLastAccessDecrement = TimeSpan.FromSeconds(_timeSinceLastCollection.TotalSeconds / 3);

			_totalNumberOfCollections = 0;
			_totalBytesCollected = 0;
			_totalLargeObjectsCollected = 0;
			_totalContainersUnloaded = 0;

			try
			{
				CodeClock clock = new CodeClock();
				clock.Start();

				Collect();

				clock.Stop();
				PerformanceReportBroker.PublishReport("Memory", "Collect", clock.Seconds);
			}
			catch (Exception e)
			{
				Platform.Log(LogLevel.Warn, e, "Default memory management strategy failed to collect.");
			}
			finally
			{
				DateTime collectionEndTime = DateTime.Now;
				if (_totalContainersUnloaded > 0)
					_lastCollectionTime = collectionEndTime;

				_largeObjectEnumerator = null;

				TimeSpan totalElapsed = collectionEndTime - _collectionStartTime;

				MemoryCollectedEventArgs finalArgs = new MemoryCollectedEventArgs(
					_totalContainersUnloaded, _totalLargeObjectsCollected, _totalBytesCollected, totalElapsed, true);

                if ( _totalNumberOfCollections != 0
                  || _totalBytesCollected != 0
                  || _totalLargeObjectsCollected != 0
			      ||_totalContainersUnloaded != 0)
				    Platform.Log(LogLevel.Info, 
					    "Large object collection summary: freed {0} MB in {1} seconds and {2} iterations, Total Containers: {3}, Total Large Objects: {4}",
					    _totalBytesCollected/(float)OneMegabyte,
					    totalElapsed.TotalSeconds,
					    _totalNumberOfCollections,
					    _totalContainersUnloaded,
					    _totalLargeObjectsCollected);

				OnMemoryCollected(finalArgs);
			}
		}
		private void CollectGarbage()
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			Platform.Log(LogLevel.Debug, "Performing garbage collection.");
			GC.Collect();

			clock.Stop();
			PerformanceReportBroker.PublishReport("Memory", "GarbageCollection", clock.Seconds);
		}
		private void Collect(long bytesToCollect)
		{
			bool continueCollecting = false;
			bool needMoreMemorySignalled = false;

			if (bytesToCollect <= 0)
			{
				Platform.Log(LogLevel.Debug,
					"Memory is not above high watermark; firing collected event to check if more memory is required.");

				MemoryCollectedEventArgs args = new MemoryCollectedEventArgs(0, 0, 0, TimeSpan.Zero, false);
				OnMemoryCollected(args);
				continueCollecting = needMoreMemorySignalled = args.NeedMoreMemory;
			}
			else
			{
				continueCollecting = true;
				Platform.Log(LogLevel.Debug, "Memory *is* above high watermark; collecting ...");
			}

			if (!continueCollecting)
				return;

			int batchSize = 10;
			int collectionNumber = 0;

			while (continueCollecting)
			{
				CodeClock clock = new CodeClock();
				clock.Start();

				long bytesCollected = 0;
				int largeObjectsCollected = 0;
				int containersUnloaded = 0;
				int i = 0;

				foreach (ILargeObjectContainer container in GetNextBatchOfContainersToCollect(batchSize))
				{
					++i;

					try
					{
						long bytesHeldBefore = container.BytesHeldCount;
						int largeObjectsHeldBefore = container.LargeObjectCount;

						container.Unload();

						long bytesHeldAfter = container.BytesHeldCount;
						int largeObjectsHeldAfter = container.LargeObjectCount;

						int largeObjectsHeldDifference = largeObjectsHeldBefore - largeObjectsHeldAfter;
						largeObjectsCollected += largeObjectsHeldDifference;
						if (largeObjectsHeldDifference > 0)
							++containersUnloaded;

						long bytesDifference = (bytesHeldBefore - bytesHeldAfter);
						bytesCollected += bytesDifference;
						_totalBytesCollected += bytesDifference;
					}
					catch (Exception e)
					{
						Platform.Log(LogLevel.Warn, e, "An unexpected error occurred while attempting to collect large object memory.");
					}

					//when needMoreMemorySignalled is true, we need to be more aggressive and keep collecting.
					if (!needMoreMemorySignalled && _totalBytesCollected >= bytesToCollect)
						break;
				}

				batchSize *= 2;

				_totalContainersUnloaded += containersUnloaded;
				_totalLargeObjectsCollected += largeObjectsCollected;
				++_totalNumberOfCollections;

				clock.Stop();

				continueCollecting = i > 0;
				if (!continueCollecting)
					continue;

				PerformanceReportBroker.PublishReport("Memory", "CollectionIteration", clock.Seconds);

				MemoryCollectedEventArgs args = new MemoryCollectedEventArgs(
					containersUnloaded, largeObjectsCollected, bytesCollected, TimeSpan.FromSeconds(clock.Seconds), false);

				OnMemoryCollected(args);

				needMoreMemorySignalled = args.NeedMoreMemory;
				continueCollecting = needMoreMemorySignalled || _totalBytesCollected < bytesToCollect;

				if (Platform.IsLogLevelEnabled(LogLevel.Debug))
				{
					Platform.Log(LogLevel.Debug, 
					             "Large object collection #{0}: freed {1} MB in {2}, Containers Unloaded: {3}, Large Objects Collected: {4}, Need More Memory: {5}, Last Batch: {6}",
					             ++collectionNumber, args.BytesCollectedCount / (float)OneMegabyte, clock,
					             containersUnloaded, largeObjectsCollected, needMoreMemorySignalled, i);
				}
			}

			CollectGarbage();
		}
Beispiel #16
0
		private static int[] ConstructFinalLut(IComposedLut outputLut, bool invert)
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			int[] outputLutData = outputLut.Data;

			if (_finalLutBuffer == null || _finalLutBuffer.Length != outputLutData.Length)
				_finalLutBuffer = new int[outputLutData.Length];

			int numberOfEntries = _finalLutBuffer.Length;

			fixed (int* pOutputLutData = outputLutData)
			{
				fixed (int* pFinalLutData = _finalLutBuffer)
				{
					int* pFinalLut = pFinalLutData;

					if (!invert)
					{
						int firstColorMappedPixelValue = outputLut.MinOutputValue;
						for (int i = 0; i < numberOfEntries; ++i)
							*(pFinalLut++) = *(pOutputLutData + i) - firstColorMappedPixelValue;
					}
					else
					{
						int lastColorMappedPixelValue = outputLut.MaxOutputValue;
						for (int i = 0; i < numberOfEntries; ++i)
							*(pFinalLut++) = lastColorMappedPixelValue - *(pOutputLutData + i);
					}
				}
			}

			clock.Stop();
			PerformanceReportBroker.PublishReport("ImageRenderer", "ConstructFinalLut", clock.Seconds);

			return _finalLutBuffer;
		}
Beispiel #17
0
		private ImageViewerComponent LoadAndOpenFiles()
		{
			var codeClock = new CodeClock();
			codeClock.Start();

			var viewer = CreateViewer(false); // don't find priors for files loaded off the local disk.
			var desktopWindow = DesktopWindow ?? Application.ActiveDesktopWindow;

			try
			{
			    UserCancelled = false;
			    bool cancelled;
                viewer.LoadImages(_filenames.ToArray(), desktopWindow, out cancelled);
                UserCancelled = cancelled;
			}
			catch (Exception e)
			{
                if (!HandleErrors)
                    throw;
            
                ExceptionHandler.Report(e, SR.MessageFailedToOpenImages, desktopWindow);
			}

            if (UserCancelled || (!AnySopsLoaded(viewer) && !AllowEmptyViewer))
			{
				viewer.Dispose();
				return null;
			}

			var args = new LaunchImageViewerArgs(WindowBehaviour) {Title = Title};
			ImageViewerComponent.Launch(viewer, args);

			codeClock.Stop();
			Platform.Log(LogLevel.Debug, string.Format("TTFI: {0}", codeClock));

			return viewer;
		}
		private void UpdateImageScroller()
		{

			//This method can be called repeatedly and will essentially be a no-op if nothing needs to change.
			//In tiled mode, it could be a little inefficient to call repeatedly, but it's the lesser of the evils.
			//Otherwise, we're subscribing to a multitude of events and updating different things at different times.
			//Not to mention, that doesn't cover every case, like sorting images.  It's nothing compared to
			//the cost of updating the scroll control itself, anyway.

			CodeClock clock = new CodeClock();
			clock.Start();

			bool visibleBefore = ImageScrollerVisible;
			bool visibleNow = false;

			IDisplaySet displaySet = _imageBox.DisplaySet;
    		if (displaySet != null)
    		{
				int tileCount = _imageBox.Tiles.Count;
				int maximum = Math.Max(0, displaySet.PresentationImages.Count - tileCount);
				if (maximum > 0)
				{
					visibleNow = true;

					int topLeftIndex = Math.Max(0, _imageBox.TopLeftPresentationImageIndex);
					_imageScroller.SetValueAndRange(topLeftIndex, 0, maximum);
					_imageScroller.Increment = Math.Max(1, tileCount);
					_imageScroller.Value = topLeftIndex;
				}
    		}

			if (visibleBefore != visibleNow)
			{
				ImageScrollerVisible = visibleNow;
				//UpdateImageScroller is only called right before a Tile is drawn, so we suppress
				//the Tile drawing as a result of a size change here.
				SetTileParentImageBoxRectangles(true);
			}

			clock.Stop();
			//Trace.WriteLine(String.Format("UpdateScroller: {0}", clock));
		}
		private ImageViewerComponent LoadAndOpenStudies()
		{
			var codeClock = new CodeClock();
			codeClock.Start();

			var viewer = CreateViewer(LoadPriors);
			var desktopWindow = DesktopWindow ?? Application.ActiveDesktopWindow;

			try
			{
				viewer.LoadStudies(_studiesToOpen);
			}
			catch (Exception e)
			{
				ExceptionHandler.Report(e, SR.MessageFailedToOpenStudy, desktopWindow);
			}

			if (!AnySopsLoaded(viewer) && !AllowEmptyViewer)
			{
				viewer.Dispose();
				return null;
			}

			var args = new LaunchImageViewerArgs(WindowBehaviour) {Title = Title};
			ImageViewerComponent.Launch(viewer, args);

			codeClock.Stop();
			Platform.Log(LogLevel.Debug, string.Format("TTFI: {0}", codeClock));

			return viewer;
		}
		private DicomFile TryClientRetrieveImageHeader(out Exception lastRetrieveException)
		{
			// retry parameters
			const int retryTimeout = 1500;
			int retryDelay = 50;
			int retryCounter = 0;

			CodeClock timeoutClock = new CodeClock();
			timeoutClock.Start();

			lastRetrieveException = null;

			while (true)
			{
				try
				{
					if (retryCounter > 0)
						Platform.Log(LogLevel.Info, "Retrying retrieve headers for Sop '{0}' (Attempt #{1})", this.SopInstanceUid, retryCounter);

					return _loader.LoadDicomFile(new LoadDicomFileArgs(this.StudyInstanceUid, this.SeriesInstanceUid, this.SopInstanceUid, true, false));
				}
				catch (Exception ex)
				{
					lastRetrieveException = ex;

					timeoutClock.Stop();
					if (timeoutClock.Seconds*1000 >= retryTimeout)
					{
						// log an alert that we are aborting (exception trace at debug level only)
						int elapsed = (int) (1000*timeoutClock.Seconds);
						Platform.Log(LogLevel.Warn, "Failed to retrieve headers for Sop '{0}'; Aborting after {1} attempts in {2} ms", this.SopInstanceUid, retryCounter, elapsed);
						Platform.Log(LogLevel.Debug, ex, "[GetHeaders Fail-Abort] Sop: {0}, Retry Attempts: {1}, Elapsed: {2} ms", this.SopInstanceUid, retryCounter, elapsed);
						break;
					}
					timeoutClock.Start();

					retryCounter++;

					// log the retry (exception trace at debug level only)
					Platform.Log(LogLevel.Warn, "Failed to retrieve headers for Sop '{0}'; Retrying in {1} ms", this.SopInstanceUid, retryDelay);
					Platform.Log(LogLevel.Debug, ex, "[GetHeaders Fail-Retry] Sop: {0}, Retry in: {1} ms", this.SopInstanceUid, retryDelay);
					
					MemoryManager.Collect(retryDelay);
					retryDelay *= 2;
				}
			}

			return null;
		}
			public byte[] GetUncompressedPixelData()
			{
                try
                {

                    //construct this object before the lock so there's no chance of deadlocking
                    //with the parent data source (because we are accessing its tags at the 
                    //same time as it's trying to get the pixel data).
                    FramePixelDataRetriever retriever = new FramePixelDataRetriever(this);

                    lock (_syncLock)
                    {
                        RetrievePixelDataResult result;
                        
                        if (_retrieveResult == null)
                        {
                            AbortAttemptIfNecessary();

                            ResetAttemptData();
                            _retrievesAttempted++;
                            
                            result = retriever.Retrieve();
                        }
                        else
                            result = _retrieveResult;

                        //free this memory up in case it's holding a compressed buffer.
                        _retrieveResult = null;

                        CodeClock clock = new CodeClock();
                        clock.Start();

                        //synchronize the call to decompress; it's really already synchronized by
                        //the parent b/c it's only called from CreateFrameNormalizedPixelData, but it doesn't hurt.
                        byte[] pixelData = result.GetPixelData();

                        clock.Stop();

                        Platform.Log(LogLevel.Debug,
                                     "[Decompress Info] Sop/Frame: {0}/{1}, Transfer Syntax: {2}, Uncompressed bytes: {3}, Elapsed (s): {4}",
                                     retriever.SopInstanceUid, FrameNumber, retriever.TransferSyntaxUid,
                                     pixelData.Length, clock.Seconds);

                        return pixelData;
                    }
                }
                catch(Exception ex)
                {
                    _lastError = ex;
                    throw;
                }
			}
Beispiel #22
0
        /// <summary>
        /// Figure out which studies have been deleted and/or updated.
        /// </summary>
        private void ProcessChangedStudiesAsync(out DateTime? queryStartTime, out List<string> deletedStudyUids, out List<StudyEntry> updatedStudies)
        {
            deletedStudyUids = new List<string>();
            updatedStudies = new List<StudyEntry>();
            queryStartTime = null;
            DateTime now = DateTime.Now;

            var fiveSeconds = TimeSpan.FromSeconds(5);
            var rapidChangeInterval = TimeSpan.FromMilliseconds(300);

            lock (_syncLock)
            {
                if (_queryingForUpdates)
                    return; //Already querying.

                //Nothing to query for? Return.
                if (_setChangedStudies.Count == 0 || !_lastStudyChangeTime.HasValue)
                    return;

                bool studiesChanging = now - _lastStudyChangeTime.Value < rapidChangeInterval;
                if (studiesChanging)
                {
                    //Many DIFFERENT studies are changing in very rapid succession. Delay until it settles down, which usually isn't long.
                    Platform.Log(LogLevel.Debug, "Studies are still actively changing - delaying update.");
                    return;
                }
                
                if (!_hastenUpdateQuery)
                {
                    bool updatedRecently = _lastUpdateQueryEndTime.HasValue && now - _lastUpdateQueryEndTime < fiveSeconds;
                    if (updatedRecently)
                    {
                        //We just finished an update query less than 5 seconds ago.
                        Platform.Log(LogLevel.Debug, "Studies were updated within the last 5 seconds - delaying update.");
                        return;
                    }
                }

                //Reset this before the immediate query.
                _hastenUpdateQuery = false;

                //Add everything to the deleted list.
                deletedStudyUids.AddRange(_setChangedStudies.Keys);
                _setChangedStudies.Clear();

                //We are officially querying for updates.
                _queryingForUpdates = true;
            }

            queryStartTime = now;
            string studyUids = DicomStringHelper.GetDicomStringArray(deletedStudyUids);

            try
            {
                var clock = new CodeClock();
                clock.Start();
             
                var criteria = new StudyRootStudyIdentifier { StudyInstanceUid = studyUids };
                var request = new GetStudyEntriesRequest { Criteria = new StudyEntry { Study = criteria } };
                
                IList<StudyEntry> entries = null;
                
                //We're doing it this way here because it's local only.
                Platform.GetService<IStudyStoreQuery>(s => entries = s.GetStudyEntries(request).StudyEntries);

                foreach (var entry in entries)
                {
                    //If we got a result back, then it's not deleted.
                    deletedStudyUids.Remove(entry.Study.StudyInstanceUid);
                    updatedStudies.Add(entry);
                }

                clock.Stop();
                Platform.Log(LogLevel.Debug, "Study update query took {0}.", clock);
            }
            catch (Exception e)
            {
                Platform.Log(LogLevel.Error, e);
            }
            finally
            {
                lock (_syncLock)
                {
                    //Finished querying for updates.
                    _queryingForUpdates = false;
                    _lastUpdateQueryEndTime = now;
                }
            }
        }
			/// <summary>
			/// Called by <see cref="StandardSopFrameData.GetNormalizedOverlayData"/> to create a new byte buffer containing normalized 
			/// overlay pixel data for a particular overlay plane.
			/// </summary>
			/// <remarks>
			/// See <see cref="StandardSopFrameData.GetNormalizedOverlayData"/> for details on the expected format of the byte buffer.
			/// </remarks>
			/// <param name="overlayNumber">The 1-based overlay plane number.</param>
			/// <returns>A new byte buffer containing the normalized overlay pixel data.</returns>
			protected override byte[] CreateNormalizedOverlayData(int overlayNumber)
			{
				//TODO (CR December 2010): make this a helper method somewhere, since it's now identical to the one in StreamingSopFrameData?

				var overlayIndex = overlayNumber - 1;

				byte[] overlayData = null;

				var clock = new CodeClock();
				clock.Start();

				// check whether or not the overlay plane exists before attempting to ascertain
				// whether or not the overlay is embedded in the pixel data
				var overlayPlaneModuleIod = new OverlayPlaneModuleIod(Parent);
				if (overlayPlaneModuleIod.HasOverlayPlane(overlayIndex))
				{
					if (_overlayCache[overlayIndex] == null)
					{
						var overlayPlane = overlayPlaneModuleIod[overlayIndex];
						if (!overlayPlane.HasOverlayData)
						{
							// if the overlay is embedded, trigger retrieval of pixel data which will populate the cache for us
							GetNormalizedPixelData();
						}
						else
						{
							// try to compute the offset in the OverlayData bit stream where we can find the overlay frame that applies to this image frame
							int overlayFrame;
							int bitOffset;
							if (overlayPlane.TryGetRelevantOverlayFrame(FrameNumber, Parent.NumberOfFrames, out overlayFrame) &&
							    overlayPlane.TryComputeOverlayDataBitOffset(overlayFrame, out bitOffset))
							{
								// offset found - unpack only that overlay frame
								var od = new OverlayData(bitOffset,
								                         overlayPlane.OverlayRows,
								                         overlayPlane.OverlayColumns,
								                         overlayPlane.IsBigEndianOW,
								                         overlayPlane.OverlayData);
								_overlayCache[overlayIndex] = od.Unpack();
							}
							else
							{
								// no relevant overlay frame found - i.e. the overlay for this image frame is blank
								_overlayCache[overlayIndex] = new byte[0];
							}
						}
					}

					overlayData = _overlayCache[overlayIndex];
				}

				clock.Stop();
				PerformanceReportBroker.PublishReport("DicomMessageSopDataSource", "CreateNormalizedOverlayData", clock.Seconds);

				return overlayData;
			}
Beispiel #24
0
		static partial void StartClock(ref CodeClock codeClock);
Beispiel #25
0
		/// <summary>
		/// Draws an <see cref="ImageGraphic"/>.
		/// </summary>
		protected override void DrawImageGraphic(ImageGraphic imageGraphic)
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			Surface.ImageBuffer.Graphics.Clear(Color.FromArgb(0x0, 0xFF, 0xFF, 0xFF));

			DrawImageGraphic(Surface.ImageBuffer, imageGraphic);

			Surface.FinalBuffer.RenderImage(Surface.ImageBuffer);

			clock.Stop();
			PerformanceReportBroker.PublishReport("GDIRenderer", "DrawImageGraphic", clock.Seconds);
		}
Beispiel #26
0
		static partial void StopClock(CodeClock codeClock, string method, int pixels, int subsamples);
Beispiel #27
0
		/// <summary>
		/// Called when <see cref="DrawArgs.DrawMode"/> is equal to <b>DrawMode.Refresh</b>.
		/// </summary>
		protected override void Refresh()
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			if (Surface.FinalBuffer != null)
				Surface.FinalBuffer.RenderToScreen();

			clock.Stop();
			PerformanceReportBroker.PublishReport("GDIRenderer", "Refresh", clock.Seconds);
		}
Beispiel #28
0
		private void DoDraw()
		{
			EventsHelper.Fire(_drawing, this, EventArgs.Empty);

			CodeClock clock = new CodeClock();
			clock.Start();

			if (this.Surface != null)
			{
				System.Drawing.Graphics graphics = this.CreateGraphics();

				this.Surface.WindowID = this.Handle;
				this.Surface.ContextID = graphics.GetHdc();
				this.Surface.ClientRectangle = this.ClientRectangle;
				this.Surface.ClipRectangle = this.ClientRectangle;

				DrawArgs args = new DrawArgs(this.Surface,
				                             new WinFormsScreenProxy(Screen.FromControl(this)),
				                             DrawMode.Render);

				_isDrawing = true;

				try
				{
					_tile.Draw(args);

					_lastRenderExceptionMessage = null;
				}
				catch (Exception ex)
				{
					Platform.Log(LogLevel.Error, ex, "An error has occured while rendering the contents of a tile.");

					_lastRenderExceptionMessage = ex is RenderingException ? ((RenderingException) ex).UserMessage : ex.Message;

					// we cannot simply pass the existing Graphics because we haven't released its hDC yet
					// if we do, we'll get a "Object is currently in use elsewhere" exception
					DrawErrorMessage(_lastRenderExceptionMessage, Surface.ContextID, ClientRectangle);
				}
				finally
				{
					graphics.ReleaseHdc(this.Surface.ContextID);
					graphics.Dispose();

					_isDrawing = false;
				}
			}

			//Cause the tile to paint/refresh.
			Invalidate();
			Update();

			clock.Stop();
			string str = String.Format("TileControl.Draw: {0}, {1}\n", clock.ToString(), this.Size.ToString());
			Trace.Write(str);
		}
Beispiel #29
0
		/// <summary>
		/// Draws the Text Overlay.
		/// </summary>
		protected void DrawTextOverlay(IPresentationImage presentationImage)
		{
			CodeClock clock = new CodeClock();
			clock.Start();

			if (presentationImage == null || !(presentationImage is IAnnotationLayoutProvider))
				return;

			IAnnotationLayout layout = ((IAnnotationLayoutProvider)presentationImage).AnnotationLayout;
			if (layout == null || !layout.Visible)
				return;

			foreach (AnnotationBox annotationBox in layout.AnnotationBoxes)
			{
				if (annotationBox.Visible)
				{
					string annotationText = annotationBox.GetAnnotationText(presentationImage);
					if (!String.IsNullOrEmpty(annotationText))
						DrawAnnotationBox(annotationText, annotationBox);
				}
			}

			clock.Stop();
			PerformanceReportBroker.PublishReport("RendererBase", "DrawTextOverlay", clock.Seconds);
		}
		private void Render(bool fullQuality, UpdateOverlayCallback updateOverlayCallback)
		{
			lock (_lockRender)
			{
				try
				{
					var mirrored = false;
					var mTime = -1;
					if (_sceneGraphRoot != null)
					{
						_sceneGraphRoot.UpdateSceneGraph(_vtkRenderer);
						mirrored = _sceneGraphRoot.ViewPortSpatialTransform.FlipX ^ _sceneGraphRoot.ViewPortSpatialTransform.FlipY;
						mTime = _sceneGraphRoot.GetMTime();
					}

					_vtkRenderWindow.SetDesiredUpdateRate(fullQuality ? _stillFrameRate : _dynamicFrameRate);

					// decide whether or not to render the VTK layer of the image based on the last modification time of the scene graph
					// do not use the renderer or renderwindow's MTime because they are affected by the update rate change above
					var renderTime = -1f;
					if (mTime > _lastRenderTime)
					{
						var renderClock = _renderClock ?? (_renderClock = new CodeClock());
						renderClock.Clear();
						renderClock.Start();

						var bmpData = ImageBuffer.Bitmap.LockBits(new Rectangle(0, 0, _clientRectangle.Width, _clientRectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
						try
						{
							_vtkRenderWindow.Render();

							// since we just rendered, the correct OpenGL context is still 'current' on this thread so we don't need to makeCurrent
							glReadBuffer(GL_FRONT_LEFT); // select the front buffer
							glDisable(GL_TEXTURE_2D); // according to VTK code, some video drivers have issues with this feature
							glPixelStorei(GL_PACK_ALIGNMENT, 4); // align to 4 byte boundaries (since we're copying 32-bit pixels anyway)

							// now read from the OpenGL buffer directly into our surface buffer
							var pData = bmpData.Stride > 0 ? bmpData.Scan0 : bmpData.Scan0 + (bmpData.Height - 1)*bmpData.Stride;
							glReadPixels(0, 0, _clientRectangle.Width, _clientRectangle.Height, GL_BGRA, OpenGlImplementation.ReadPixelsTypeBgra, pData);

							// OpenGL buffer data is a bottom-up image, and the GDI+ memory bitmap might be top-bottom, so we flip the scan lines here
							if (bmpData.Stride > 0)
								FlipImage(bmpData.Scan0, bmpData.Height, bmpData.Stride);
						}
						finally
						{
							ImageBuffer.Bitmap.UnlockBits(bmpData);

							// only record the last render time for full quality renders only
							if (fullQuality) _lastRenderTime = mTime;
						}

						renderClock.Stop();
						renderTime = renderClock.Seconds;

						// perform a single horizontal flip here if necessary, since the VTK camera does not support a mirrorred view port
						ImageBuffer.Bitmap.RotateFlip(mirrored ? RotateFlipType.RotateNoneFlipX : RotateFlipType.RotateNoneFlipNone);
					}

					if (renderTime >= 0)
					{
						if (VtkPresentationImageRenderer.ShowFps)
						{
							var font = _gdiFont ?? (_gdiFont = new Font(FontFamily.GenericMonospace, 12, FontStyle.Bold, GraphicsUnit.Point));
							var msg = string.Format("FPS: {0,6}", renderTime >= 0.000001 ? (1/renderTime).ToString("f1") : "------");
							ImageBuffer.Graphics.DrawString(msg, font, Brushes.Black, 11, 11);
							ImageBuffer.Graphics.DrawString(msg, font, Brushes.White, 10, 10);

							if (fullQuality)
							{
								msg = string.Format("TTI: {0,6} ms", renderTime >= 0.000001 ? (renderTime*1000).ToString("f1") : "------");
								ImageBuffer.Graphics.DrawString(msg, font, Brushes.Black, 11, 15 + 11);
								ImageBuffer.Graphics.DrawString(msg, font, Brushes.White, 10, 15 + 10);
							}
						}
					}

					if (fullQuality)
					{
						if (_reportRenderingPerformance && _statRenderFrameCount > 0 && _statRenderDuration > 0.000001)
						{
							var avgLowFrameRate = _statRenderFrameCount/_statRenderDuration;
							Platform.Log(LogLevel.Info, "VTKRenderer: LOD FPS: {0:f1} ({1} frame(s) in {2:f1} ms); FINAL: {3:f1} ms", avgLowFrameRate, _statRenderFrameCount, _statRenderDuration*1000, renderTime*1000);
						}
						_statRenderFrameCount = 0;
						_statRenderDuration = 0;
					}
					else
					{
						_statRenderDuration += renderTime;
						++_statRenderFrameCount;
					}

					if (updateOverlayCallback != null)
						updateOverlayCallback.Invoke();

					FinalBuffer.RenderImage(ImageBuffer);
					FinalBuffer.RenderImage(OverlayBuffer);
					FinalBuffer.RenderToScreen();

					EventsHelper.Fire(_invalidated, this, new EventArgs());
				}
				catch (Exception ex)
				{
					Platform.Log(LogLevel.Error, ex, "VTK Rendering Exception");
				}
			}
		}