/*=============================================================================
Project: SharpImage
Module: siGdiSliceRenderer.cs
Language: C#
Author: Dan Mueller
Date: $Date: 2008-01-11 14:55:13 +1000 (Fri, 11 Jan 2008) $
Revision: $Revision: 30 $
Copyright (c) Queensland University of Technology (QUT) 2007.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=============================================================================*/
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using SharpImage.Main;
namespace SharpImage.Rendering
{
#region SliceChangedEventArgs Class
//=========================================================================
public class siSliceChangedEventArgs : EventArgs
{
private IWin32Window m_Window;
private Point m_Location;
private ScrollEventArgs m_ScrollArgs;
private int m_Slice;
public siSliceChangedEventArgs(IWin32Window window, Point location,
ScrollEventArgs scrollArgs, int slice) : base()
{
m_Window = window;
m_Location = location;
m_ScrollArgs = scrollArgs;
m_Slice = slice;
}
public IWin32Window Window
{
get { return this.m_Window; }
}
public Point Location
{
get { return this.m_Location; }
}
public ScrollEventArgs ScrollArgs
{
get { return this.m_ScrollArgs; }
}
public int Slice
{
get { return this.m_Slice; }
}
}
//=========================================================================
#endregion
#region siGdiSliceRenderer Class
//=========================================================================
///
/// Renders 2-D and 3-D images as slices along the z-axis using the Windows
/// GDI+ (System.Drawing) library. Multiple inputs are rendered from Input[0]
/// and up (eg. Input[1] will overwrite Input[0] if it contains no
/// transparency). The input image assumes the default lookup table unless
/// the image is tagged with the Metadata key "LookupTable{0}".
///
public class siGdiSliceRenderer : siRenderer, IDisposable
{
#region Constants
//=====================================================================
protected static readonly Font s_ImageInfoFont = new Font("Arial", 9.0F);
protected static readonly SolidBrush s_ImageInfoBrush = new SolidBrush(Color.Black);
protected static readonly Color s_ImageBorderColor = Color.Black;
protected static readonly Color s_BackColor = Color.White;
protected const int SPACE_AROUND_IMAGE = 6;
protected const double MINIMUM_SCALE_FACTOR = 0.25;
//=====================================================================
#endregion
#region Instance Variables
//=====================================================================
private bool m_HasRenderedFirstTime = false;
private bool m_IsDisposed = false;
private ToolTip m_ToolTipSlice;
private VScrollBar m_SliceSlider;
//=====================================================================
#endregion
#region Construction and Disposal
//=====================================================================
///
/// Public constructor.
///
public siGdiSliceRenderer(IApplication parent)
: base("GDI Viewer", parent, new siGdiRendererForm())
{
// Setup form
this.BackColor = s_BackColor;
// Setup Metadata
this.Metadata["IsTranslatingImage"] = false;
this.Metadata["IsMouseInsideImageSpace"] = false;
this.Metadata["ZoomFactor"] = (double)1.0;
this.Metadata["PaintBorder"] = true;
this.Metadata["ArrayIndex"] = 0U;
// Initialise slider
m_SliceSlider = new VScrollBar();
m_SliceSlider.Dock = DockStyle.Right;
m_SliceSlider.MouseEnter += new EventHandler(m_SliceSlider_MouseEvent);
m_SliceSlider.MouseHover += new EventHandler(m_SliceSlider_MouseEvent);
m_SliceSlider.Scroll += new ScrollEventHandler(m_SliceSlider_Scroll);
this.Form.Controls.Add(this.m_SliceSlider);
// Initialise tooltip
this.m_ToolTipSlice = new ToolTip();
this.m_ToolTipSlice.UseFading = false;
// Add event handlers
this.SliceChanged += new SliceChangedHandler(GdiSliceRenderer_SliceChanged);
}
///
/// Deconstructor.
///
~siGdiSliceRenderer()
{
// Call Dispose with false. Since we're in the
// destructor call, the managed resources will be
// disposed of anyways.
this.Dispose(false);
}
///
/// Dispose of any resources.
///
public override void Dispose()
{
// Dispose both managed and unmanaged resources
this.Dispose(true);
// Tell the GC that the Finalize process no longer needs
// to be run for this object.
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposeManagedResources)
{
// Check that we are not already disposed
if (this.m_IsDisposed)
return;
// TODO: At the moment, if an image is a label it is NEVER disposed of
// by this method (it must rely on reference counting to dispose
// of it automatically when it is no longer referenced).
// Only get here if not already disposed
if (disposeManagedResources)
{
// Clean up bitmaps
if (this.InputsAsBitmap != null)
{
foreach (Bitmap inputAsBitmap in this.InputsAsBitmap)
{
if (inputAsBitmap != null)
inputAsBitmap.Dispose();
}
this.InputsAsBitmap.Clear();
this.m_InputsAsBitmap = null;
}
// Clean up scalar images
if (this.InputsAsScalar != null)
{
foreach (itk.itkImageBase input in this.InputsAsScalar)
{
if (input != null && !input.IsDisposed &&
!input.Metadata.ContainsKey("IsLabel"))
{
input.DisconnectPipeline();
input.Dispose();
}
}
this.InputsAsScalar.Clear();
this.m_InputsAsScalar = null;
}
// Clean up slice images
if (this.InputsAsSlice != null)
{
foreach (itk.itkImageBase input in this.InputsAsSlice)
{
if (input != null && !input.IsDisposed &&
!input.Metadata.ContainsKey("IsLabel"))
{
input.DisconnectPipeline();
input.Dispose();
}
}
this.InputsAsSlice.Clear();
this.m_InputsAsSlice = null;
}
// Clean up input images
if (this.Inputs != null)
{
foreach (itk.itkDataObject input in this.Inputs)
{
if (input is itk.itkImageBase)
{
// NOTE: Labels are not disposed when the renderer closes because
// they maybe added to another renderer.
if (input != null && !input.IsDisposed &&
!input.Metadata.ContainsKey("IsLabel"))
{
input.DisconnectPipeline();
if (input is IDisposable) (input as IDisposable).Dispose();
}
}
// NOTE: Paths are not disposed when the renderer closes because
// they maybe added to another renderer.
//else if (input is itk.IPolyLineParametricPath)
//{
// input.DisconnectPipeline();
// if (input is IDisposable) (input as IDisposable).Dispose();
//}
}
this.Inputs.Clear();
}
// Clean up slider
if (this.m_SliceSlider != null)
{
this.m_SliceSlider.Dispose();
this.m_SliceSlider = null;
}
}
// Dispose base
base.Dispose();
this.m_IsDisposed = true;
}
//=====================================================================
#endregion
#region Properties
//=====================================================================
#region Slice
//=====================================================================
///
/// Gets/sets the current slice displayed by the renderer.
/// NOTE: The renderer is not repainted, to refresh the display call
/// Repaint() after calling this.
///
public int Slice
{
get { return (int)this.Metadata["CurrentSlice"]; }
set
{
//this.m_SliceSlider_Scroll(this.m_SliceSlider, new ScrollEventArgs(ScrollEventType.SmallIncrement, this.Slice, value));
if (!this.m_HasRenderedFirstTime)
return;
// Set the slice value
this.m_SliceSlider.Value = value;
int slice = value;
// Update the slice
this.Metadata["CurrentSlice"] = slice;
for (int i = 0; i < this.Inputs.Count; i++)
{
this.ConvertScalarToSlice(i);
this.ConvertSliceToBitmap(i);
}
}
}
//=====================================================================
#endregion
#region ZoomFactor
//=====================================================================
///
/// Gets/sets the zoom factor for viewing the image.
/// A zoom factor of 1.0 means we render 1:1.
/// A zoom factor of 2.0 means we render 2:1 (zoomed in).
/// A zoom factor of 0.5 means we render 1:2 (zoomed out).
///
public double ZoomFactor
{
get { return (double)this.Metadata["ZoomFactor"]; }
set { this.Metadata["ZoomFactor"] = value; }
}
//=====================================================================
#endregion
#region PaintBorder
//=====================================================================
///
/// Gets/sets if a border is drawn around the slice.
///
public bool PaintBorder
{
get { return (bool)this.Metadata["PaintBorder"]; }
set { this.Metadata["PaintBorder"] = value; }
}
//=====================================================================
#endregion
#region Offset
//=====================================================================
///
/// Gets/sets the top-left image offset.
/// This property controls image translation.
///
protected PointF Offset
{
get
{
if (!this.ContainsMetadata("ImageScreenOffset"))
this.Offset = new PointF(0.0F, 0.0F);
return (PointF)this.Metadata["ImageScreenOffset"];
}
set { this.Metadata["ImageScreenOffset"] = value; }
}
//=====================================================================
#endregion
#region ArrayIndex
//=====================================================================
///
/// Gets/sets the index to view for itkPixels with an array type.
/// This property has no effect for Scalar images.
///
protected uint ArrayIndex
{
get { return (uint)this.Metadata["ArrayIndex"]; }
set { this.Metadata["ArrayIndex"] = value; }
}
//=====================================================================
#endregion
#region InputsAsScalar
//=====================================================================
private List m_InputsAsScalar = new List();
///
/// Gets the list of Input objects as scalar (not array) images.
///
public List InputsAsScalar
{
get { return this.m_InputsAsScalar; }
}
//=====================================================================
#endregion
#region InputsAsSlice
//=====================================================================
private List m_InputsAsSlice = new List();
///
/// Gets the list of Input objects as image slices.
///
public List InputsAsSlice
{
get { return this.m_InputsAsSlice; }
}
//=====================================================================
#endregion
#region InputsAsRescaled
//=====================================================================
private List m_InputsAsRescaled = new List();
///
/// Gets the list of Input objects as a rescaled UC image.
///
public List InputsAsRescaled
{
get { return this.m_InputsAsRescaled; }
}
//=====================================================================
#endregion
#region InputsAsBitmap
//=====================================================================
private List m_InputsAsBitmap = new List();
///
/// Gets the list of image slices as renderable bitmaps.
///
protected List InputsAsBitmap
{
get { return this.m_InputsAsBitmap; }
}
//=====================================================================
#endregion
//=====================================================================
#endregion
#region Events
//=====================================================================
#region SliceChanged
//=====================================================================
///
/// EventArg delegate for allowing listeners access to the
/// RendererBase which rasied the event.
///
///
public delegate void SliceChangedHandler(siGdiSliceRenderer renderer, siSliceChangedEventArgs e);
private SliceChangedHandler m_StorageSliceChanged;
///
/// An event raised when the current slice has been changed.
///
public event SliceChangedHandler SliceChanged
{
add { this.m_StorageSliceChanged += value; }
remove { this.m_StorageSliceChanged -= value; }
}
///
/// Fire the SliceChanged event.
///
protected void FireSliceChanged(siSliceChangedEventArgs e)
{
// Fire event
if (this.m_StorageSliceChanged != null)
this.m_StorageSliceChanged(this, e);
}
//=====================================================================
#endregion
//=====================================================================
#endregion
#region Public Methods
//=====================================================================
///
/// Initialises the GdiSliceRenderer.
///
/// This method is thread-safe.
public override void Initialise()
{
// Call the base
base.Initialise();
// Make the call thread-safe
if (this == null || this.Form == null)
{
return;
}
else if (this.Form.InvokeRequired)
{
this.Form.Invoke(new VoidMethodCallback(this.Initialise));
return;
}
// Initialise form
this.Form.Initialise(this);
// Ensure the default slice is set
this.InitialiseInputs();
// Get the input and inputAsSlice
const int indexInput = 0;
itk.itkImageBase input = this.InputAsImage(indexInput);
itk.itkImageBase inputAsSlice = this.InputsAsSlice[indexInput];
// Compute the size of the information string
String infoString = String.Format("{0} {1} {2}", input.PixelType.LongTypeString, input.Size, input.Spacing);
SizeF sizeInfoString = this.GetFormGraphics().MeasureString(infoString, s_ImageInfoFont);
sizeInfoString.Height += 2.0F;
// Set starting size
double[] scaledSize = new double[inputAsSlice.Dimension];
scaledSize[0] = this.ZoomFactor * inputAsSlice.Size[0];
for (int dim = 1; dim < inputAsSlice.Dimension; dim++)
scaledSize[dim] = this.ZoomFactor * (double)inputAsSlice.Size[dim] * (inputAsSlice.Spacing[dim] / inputAsSlice.Spacing[0]);
this.ViewportSize = new Size((int)Math.Ceiling(scaledSize[0] + 2*SPACE_AROUND_IMAGE + 14),
(int)Math.Ceiling(scaledSize[1] + 2*SPACE_AROUND_IMAGE + sizeInfoString.Height));
// Set slider max value
this.m_SliceSlider.SmallChange = 1;
this.m_SliceSlider.LargeChange = 10;
this.m_SliceSlider.Minimum = 0;
// Disable slider if input image has 2 dimensions
if (input.Dimension == 2)
this.m_SliceSlider.Enabled = false;
else if (input.Dimension == 3)
this.m_SliceSlider.Maximum = input.Size[2] + this.m_SliceSlider.LargeChange - 2;
}
///
/// Initialises the slice to renderable.
///
/// This method is thread-safe.
public void InitialiseInputsAsSlice()
{
// Make the call thread-safe
if (this == null || this.Form == null)
{
return;
}
else if (this.Form.InvokeRequired)
{
this.Form.Invoke(new VoidMethodCallback(this.InitialiseInputsAsSlice));
return;
}
// Ensure the default slice is set
if (!this.ContainsMetadata("CurrentSlice"))
this.Metadata["CurrentSlice"] = 0;
// Update the slice to render
for (int i = 0; i < this.Inputs.Count; i++)
this.ConvertSliceToBitmap(i);
}
///
/// Initialises the image to slice to renderable.
///
/// This method is thread-safe.
public void InitialiseInputs()
{
// Make the call thread-safe
if (this == null || this.Form == null)
{
return;
}
else if (this.Form.InvokeRequired)
{
this.Form.Invoke(new VoidMethodCallback(this.InitialiseInputs));
return;
}
// Ensure the default slice is set
if (!this.ContainsMetadata("CurrentSlice"))
this.Metadata["CurrentSlice"] = 0;
// Convert the input to a renderable bitmap
for (int i = 0; i < this.Inputs.Count; i++)
this.ConvertInputToBitmap(i);
}
///
/// Forces the Renderer to repaint.
///
/// This method is thread-safe.
public override void Repaint()
{
// Make the call thread-safe
if (this == null || this.Form == null)
{
return;
}
else if (this.Form.InvokeRequired)
{
this.Form.Invoke(new VoidMethodCallback(this.Repaint));
return;
}
// Force the form to repaint
if (this.Visible)
this.Form.Repaint();
}
///
/// Close the RendererBase and dispose of all resources.
///
/// This method is thread-safe.
public override void Close()
{
// Make the call thread-safe
if (this == null || this.Form == null)
{
return;
}
else if (this.Form.InvokeRequired)
{
this.Form.Invoke(new VoidMethodCallback(this.Close));
return;
}
// Close and dispose
base.Close();
this.Dispose();
}
///
/// Adds the given image as a label overlayed on the other inputs.
///
///
public void AddInputAsLabel(itk.itkImageBase label, siLookupTable lut)
{
int index = this.Inputs.Count;
string keyLUT = String.Format("LookupTable{0}", index);
label.Metadata[keyLUT] = lut;
label.Metadata["IsLabel"] = true;
this.Inputs.Add(label);
this.InitialiseInputs();
}
///
/// Clears all the label inputs and repaints the renderer.
///
public void ClearLabels()
{
List inputsToRemove = new List();
foreach (itk.itkDataObject input in this.Inputs)
if (input is itk.itkImageBase && input.Metadata.ContainsKey("IsLabel"))
inputsToRemove.Add(input);
foreach (itk.itkDataObject inputToRemove in inputsToRemove)
this.Inputs.Remove(inputToRemove);
this.InitialiseInputs();
this.Repaint();
}
///
/// Clears all the Path inputs and repaints the renderer.
///
public void ClearPaths()
{
List inputsToRemove = new List();
foreach (itk.itkDataObject input in this.Inputs)
if (input is itk.IPolyLineParametricPath) inputsToRemove.Add(input);
foreach (itk.itkDataObject inputToRemove in inputsToRemove)
this.Inputs.Remove(inputToRemove);
this.Repaint();
}
///
/// Returns a bitmap which represents a "screen capture".
///
///
public override Bitmap GetScreenCapture()
{
// Get the image rectangle in screen coords
RectangleF rectfImageScreen = (RectangleF)this.Metadata["ImageScreenRectangle"];
int x = (int)rectfImageScreen.X;
int y = (int)rectfImageScreen.Y;
int width = (int)rectfImageScreen.Width;
int height = (int)rectfImageScreen.Height;
Rectangle rectScreen = new Rectangle(0, 0, width + x, height + y);
// Create the bitmap to render to
Bitmap bitmapScreen = new Bitmap(rectScreen.Width, rectScreen.Height);
Graphics gScreen = Graphics.FromImage(bitmapScreen);
gScreen.Clear(Color.Black);
// Paint to the bitmap
bool oldPaintBorder = this.PaintBorder;
this.PaintBorder = false;
this.OnPaint(new PaintEventArgs(gScreen, rectScreen));
gScreen.Dispose();
this.PaintBorder = oldPaintBorder;
// Crop the screen image
Rectangle rectCrop = new Rectangle(0, 0, width, height);
Bitmap bitmapCrop = new Bitmap(width, height);
Graphics gCrop = Graphics.FromImage(bitmapCrop);
gCrop.DrawImageUnscaled(bitmapScreen, -x, -y);
gCrop.Dispose();
bitmapScreen.Dispose();
// Return
return bitmapCrop;
}
//=====================================================================
#endregion
#region Paint Methods
//=====================================================================
protected override void OnPaint(PaintEventArgs e)
{
// Get the first input for various information purposes
itk.itkImageBase input = this.InputAsImage(0);
// Get a graphics object for drawing offscreen
int widthOffscreen = (int)e.Graphics.VisibleClipBounds.Width;
int heightOffscreen = (int)e.Graphics.VisibleClipBounds.Height;
if (widthOffscreen <= 0) widthOffscreen = 1;
if (heightOffscreen <= 0) heightOffscreen = 1;
Bitmap bitmapOffscreen = new Bitmap(widthOffscreen, heightOffscreen, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bitmapOffscreen);
// Set text rendering hint
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
// Set the form title
double zoomAsPercent = this.ZoomFactor*100.0;
this.TitleText = String.Format("{0} [{1}%]", Path.GetFileName(input.Name), zoomAsPercent.ToString());
// Draw the image information in the top left hand corner.
// NOTE: It is drawn first so that the image will overwrite it,
// if the form is resized as such.
String infoString = String.Format("{0} {1} {2}", input.PixelType.LongTypeString, input.Size, input.Spacing);
g.DrawString(infoString, s_ImageInfoFont, s_ImageInfoBrush, (float)SPACE_AROUND_IMAGE, 0F);
SizeF sizeInfoString = g.MeasureString(infoString, s_ImageInfoFont);
sizeInfoString.Height += 2.0F;
// Draw each input
for (int indexInput = 0; indexInput < this.Inputs.Count; indexInput++)
this.DrawInput(indexInput, g, sizeInfoString.Height);
// Draw the offscreen bitmap to the graphics
e.Graphics.DrawImageUnscaled(bitmapOffscreen, 0, 0);
// Record that we have rendered
this.m_HasRenderedFirstTime = true;
// Give the base class a chance to paint
base.OnPaint(e);
}
///
// Draws the given input using the Graphics g.
///
/// The index of the input to draw.
/// The Graphics object for rendering.
/// The height the size info in the top-left corner.
private void DrawInput(int indexInput, Graphics g, float sizeInfoHeight)
{
if (this.Inputs[indexInput] is itk.IPolyLineParametricPath)
this.DrawInputAsPolyLineParametricPath(indexInput, g, sizeInfoHeight);
else if (this.Inputs[indexInput] is itk.itkImageBase)
this.DrawInputAsImage(indexInput, g, sizeInfoHeight);
}
///
// Draws the given input as a PolyLineParametricPath using the Graphics g.
///
/// The index of the input to draw.
/// The Graphics object for rendering.
/// The height the size info in the top-left corner.
private void DrawInputAsPolyLineParametricPath(int indexInput, Graphics g, float sizeInfoHeight)
{
// Set antialias smoothing mode
SmoothingMode oldSmothingMode = g.SmoothingMode;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Get path, vertices and points
itk.itkImageBase inputImage = this.InputAsImage(0);
itk.itkParametricPath pathAsParametricPath = this.Inputs[indexInput] as itk.itkParametricPath;
itk.IPolyLineParametricPath path = this.Inputs[indexInput] as itk.IPolyLineParametricPath;
itk.itkContinuousIndex[] vertices = path.GetVertexList();
List> paths = new List>();
if (inputImage.Dimension == 2 && pathAsParametricPath.Dimension == 2)
{
List points = new List();
paths.Add(points);
for (int i = 0; i < vertices.Length; i++)
points.Add(this.TransformImageContinuousIndexToScreenPoint(vertices[i]));
}
else if (inputImage.Dimension == 2 && pathAsParametricPath.Dimension == 3)
{
List points = new List();
paths.Add(points);
for (int i = 0; i < vertices.Length; i++)
points.Add(this.TransformImageContinuousIndexToScreenPoint(vertices[i]));
}
else if (inputImage.Dimension == 3 && pathAsParametricPath.Dimension == 3)
{
int lasti = -2;
List points = new List();
for (int i = 0; i < vertices.Length; i++)
{
if (i - 1 != lasti)
{
points = new List();
paths.Add(points);
}
if (Math.Abs(vertices[i][2] - (double)this.Slice) <= 0.75)
{
points.Add(this.TransformImageContinuousIndexToScreenPoint(vertices[i]));
lasti = i;
}
}
}
// Get path metadata
Color color = Color.Red;
if (pathAsParametricPath.Metadata.ContainsKey("Color"))
color = (Color)pathAsParametricPath.Metadata["Color"];
float width = 1.5F;
if (pathAsParametricPath.Metadata.ContainsKey("Width"))
width = Convert.ToSingle(pathAsParametricPath.Metadata["Width"]);
// Create a pen to draw the path
Brush brush = new SolidBrush(color);
Pen pen = new Pen(brush, width);
// Draw each path
if (paths.Count > 0)
{
foreach (List points in paths)
{
if (points.Count > 1)
{
g.DrawLines(pen, points.ToArray());
}
}
}
// Reset smoothing mode
g.SmoothingMode = oldSmothingMode;
}
///
// Draws the given input as an image using the Graphics g.
///
/// The index of the input to draw.
/// The Graphics object for rendering.
/// The height the size info in the top-left corner.
private void DrawInputAsImage(int indexInput, Graphics g, float sizeInfoHeight)
{
// Set to nearest neighbour interpolation
InterpolationMode oldInterpolationMode = g.InterpolationMode;
g.InterpolationMode = InterpolationMode.NearestNeighbor;
// Get the input image data
itk.itkImageBase inputAsSlice = this.InputsAsSlice[indexInput];
Bitmap inputAsBitmap = this.InputsAsBitmap[indexInput];
if (inputAsSlice == null) return;
if (inputAsBitmap == null) return;
// Draw the scaled bitmap to the graphics handle
double[] scaledSize = new double[inputAsSlice.Dimension];
scaledSize[0] = this.ZoomFactor * (double)inputAsSlice.Size[0];
for (int dim = 1; dim < inputAsSlice.Dimension; dim++)
scaledSize[dim] = this.ZoomFactor * (double)inputAsSlice.Size[dim] * (inputAsSlice.Spacing[dim] / inputAsSlice.Spacing[0]);
// Adjust offset for zoom factor
PointF offset = this.Offset;
offset.X *= (float)this.ZoomFactor;
offset.Y *= (float)this.ZoomFactor;
// Compute image rectangle
double scaledX = Math.Ceiling(((double)this.ViewportSize.Width / 2.0) - (scaledSize[0] / 2.0) + offset.X);
double scaledY = Math.Ceiling(((double)this.ViewportSize.Height / 2.0) - (scaledSize[1] / 2.0) + (sizeInfoHeight / 2.0) + offset.Y);
double scaledWidth = Math.Floor(scaledSize[0]);
double scaledHeight = Math.Floor(scaledSize[1]);
RectangleF rectDest = new RectangleF((float)(scaledX),
(float)(scaledY),
(float)(scaledWidth),
(float)(scaledHeight));
RectangleF rectSource = new RectangleF((float)(0.0),
(float)(0.0),
(float)(inputAsSlice.Size[0]),
(float)(inputAsSlice.Size[1]));
// Adjust for index being in center of pixel
if (this.ZoomFactor > 1.0)
rectSource.Offset(-0.5F, -0.5F);
// Draw transparency squares (if required)
if (inputAsBitmap.PixelFormat == PixelFormat.Format32bppArgb)
{
// Draw a background indicating transparency
int width = inputAsBitmap.Width;
int height = inputAsBitmap.Height;
Bitmap back = siColorHelper.CreateTransparentBackground(width, height);
g.DrawImage(back, rectDest, rectSource, GraphicsUnit.Pixel);
}
// Draw the image
g.DrawImage(inputAsBitmap, rectDest, rectSource, GraphicsUnit.Pixel);
// Do some "first-input-only" processing
if (indexInput == 0)
{
// Draw a border around the image
if (this.PaintBorder)
{
Pen pen = new Pen(s_ImageBorderColor, 1.0F);
g.DrawRectangle(pen, (float)scaledX - 1.0F,
(float)scaledY - 1.0F,
(float)scaledWidth,
(float)scaledHeight);
}
// Save the rectangle
this.Metadata["ImageScreenRectangle"] = rectDest;
}
// Reset interpolation mode
g.InterpolationMode = oldInterpolationMode;
}
//=====================================================================
#endregion
#region Event Handler Methods
//=====================================================================
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
// Check we have rendered
if (!this.m_HasRenderedFirstTime)
return;
// Save the last screen mouse down
this.Metadata["LastScreenMouseDown"] = e;
// Convert the screen mouse location to image physical location
RectangleF rectImageScreen = (RectangleF)this.Metadata["ImageScreenRectangle"];
this.Metadata["LastImagePointMouseDown"] = this.Metadata["LastImagePointMouseMove"];
this.Metadata["LastImageIndexMouseDown"] = this.Metadata["LastImageIndexMouseMove"];
this.Metadata["LastImagePixelMouseDown"] = this.Metadata["LastImagePixelMouseMove"];
// Change Metadata if required
if (e.Button == MouseButtons.Right)
{
this.Metadata["IsTranslatingImage"] = true;
this.Cursor = Cursors.Hand;
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
// Change is we are translating
if (e.Button == MouseButtons.Right)
this.Metadata["IsTranslatingImage"] = false;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// Check we have rendered
if (!this.m_HasRenderedFirstTime)
return;
// Convert the screen mouse location to image pixel location
itk.itkIndex index = this.TransformScreenPointToImageIndex(e.Location);
// Save the old cursor value
Cursor newCursor = this.Cursor;
// Check the Metadata of the mouse move
if ((bool)this.Metadata["IsTranslatingImage"])
{
// We are translating the image
MouseEventArgs last = this.Metadata["LastScreenMouseMove"] as MouseEventArgs;
newCursor = Cursors.Hand;
PointF newOffset = new PointF(this.Offset.X, this.Offset.Y);
newOffset.X += (1.0F / (float)this.ZoomFactor * (float)(e.X - last.X));
newOffset.Y += (1.0F / (float)this.ZoomFactor * (float)(e.Y - last.Y));
this.Offset = newOffset;
this.Repaint();
}
else
{
// The location is inside the image
// Convert the screen mouse location to image physical location
// NOTE: This value does NOT take into account the image direction cosines
itk.itkPoint point = this.TransformScreenPointToImagePoint(e.Location);
// Update the Metadata
this.Metadata["LastImagePointMouseMove"] = point;
this.Metadata["LastImageIndexMouseMove"] = index;
bool isMouseInsideImageSpace = this.InputAsImage(0).LargestPossibleRegion.IsInside(index);
this.Metadata["IsMouseInsideImageSpace"] = isMouseInsideImageSpace;
if (isMouseInsideImageSpace)
this.Metadata["LastImagePixelMouseMove"] = this.InputAsImage(0).GetPixel(index);
else
this.Metadata["LastImagePixelMouseMove"] = null;
if (isMouseInsideImageSpace)
newCursor = Cursors.Cross;
else
newCursor = Cursors.Default;
}
// Set the form cursor
if (this.Cursor != Cursors.WaitCursor)
{
if (this.ContainsMetadata("Cursor"))
this.Cursor = (Cursor)this.Metadata["Cursor"];
else
this.Cursor = newCursor;
}
// Save the last screen mouse move
this.Metadata["LastScreenMouseMove"] = e;
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
if (e.Delta > 0)
this.ZoomOutByFactor();
else
this.ZoomInByFactor();
this.Repaint();
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
// Zoom in or out
if (e.KeyCode == Keys.Add || e.KeyCode == Keys.Oemplus)
this.ZoomInByFactor();
else if (e.KeyCode == Keys.Subtract || e.KeyCode == Keys.OemMinus)
this.ZoomOutByFactor();
// Array index
else if (e.KeyCode == Keys.NumPad0 || e.KeyCode == Keys.D0)
this.SetArrayIndex(0U);
else if (e.KeyCode == Keys.NumPad1 || e.KeyCode == Keys.D1)
this.SetArrayIndex(1U);
else if (e.KeyCode == Keys.NumPad2 || e.KeyCode == Keys.D2)
this.SetArrayIndex(2U);
else if (e.KeyCode == Keys.NumPad3 || e.KeyCode == Keys.D3)
this.SetArrayIndex(3U);
else if (e.KeyCode == Keys.NumPad4 || e.KeyCode == Keys.D4)
this.SetArrayIndex(4U);
else if (e.KeyCode == Keys.NumPad5 || e.KeyCode == Keys.D5)
this.SetArrayIndex(5U);
else if (e.KeyCode == Keys.NumPad6 || e.KeyCode == Keys.D6)
this.SetArrayIndex(6U);
else if (e.KeyCode == Keys.NumPad7 || e.KeyCode == Keys.D7)
this.SetArrayIndex(7U);
else if (e.KeyCode == Keys.NumPad8 || e.KeyCode == Keys.D8)
this.SetArrayIndex(8U);
else if (e.KeyCode == Keys.NumPad9 || e.KeyCode == Keys.D9)
this.SetArrayIndex(9U);
// Force a repaint
this.Repaint();
}
private void SetArrayIndex(uint index)
{
if (this.Inputs[0] is itk.itkImageBase)
{
itk.itkImageBase input = this.Inputs[0] as itk.itkImageBase;
if (input.PixelType.ArrayAsEnum == itk.itkPixelArrayEnum.ArrayVector ||
input.PixelType.ArrayAsEnum == itk.itkPixelArrayEnum.ArrayCovariantVector ||
input.PixelType.ArrayAsEnum == itk.itkPixelArrayEnum.ArrayVariableLengthVector ||
input.PixelType.ArrayAsEnum == itk.itkPixelArrayEnum.ArrayVectorImage)
{
if (index >= 0 && index < input.Dimension)
{
this.ArrayIndex = index;
this.InitialiseInputs();
}
}
}
}
void GdiSliceRenderer_SliceChanged(siGdiSliceRenderer renderer, siSliceChangedEventArgs e)
{
if (e.ScrollArgs.Type != ScrollEventType.EndScroll)
this.m_ToolTipSlice.Show(e.Slice.ToString("#000"), e.Window, e.Location, 350);
else
this.m_ToolTipSlice.Hide(e.Window);
}
void m_SliceSlider_Scroll(object sender, ScrollEventArgs e)
{
if (e.Type == ScrollEventType.EndScroll)
return;
if (!this.m_HasRenderedFirstTime)
return;
// Set the slice
int slice = e.NewValue;
// Update the slice
this.Metadata["CurrentSlice"] = slice;
for (int i = 0; i < this.Inputs.Count; i++)
{
this.ConvertScalarToSlice(i);
this.ConvertSliceToBitmap(i);
}
// Caculate the position near the slider control
// NOTE: The ScrollBar control does not have a way to get
// the mouse position during a change so we must
// do this dodgy calculation...
int controlBoxHeight = 20;
int y = (int)((double)(this.m_SliceSlider.Height - controlBoxHeight) * (double)this.m_SliceSlider.Value / (double)this.m_SliceSlider.Maximum);
y += controlBoxHeight + 10;
int x = this.m_SliceSlider.Left + this.m_SliceSlider.Width + 10;
// Raise the SliceChanged event
siSliceChangedEventArgs args = new siSliceChangedEventArgs(this.Form, new Point(x, y), e, e.NewValue);
this.FireSliceChanged(args);
// Force a repaint
this.Repaint();
}
void m_SliceSlider_MouseEvent(object sender, EventArgs e)
{
this.m_SliceSlider.Cursor = Cursors.Default;
}
//=====================================================================
#endregion
#region Rendering Helper Methods
//=====================================================================
///
/// Returns the given input index as an itk.itkImageBase.
/// Throws an exception if the index is invalid or the input is not
/// an itk.itkImageBase.
///
///
///
private itk.itkImageBase InputAsImage(int index)
{
// Check the input is supported
if (this.Inputs.Count < index || this.Inputs[index] == null)
throw new ApplicationException("The " + this.TypeName + " Input[" + index.ToString() + "] is invalid.");
else if (this.Inputs[index] is itk.itkImageBase)
// Return
return this.Inputs[index] as itk.itkImageBase;
else
throw new NotSupportedException("The " + this.TypeName + " does not support the type of Input[" + index.ToString() + "].");
}
///
/// This method converts the given input index object into a bitmap
/// For example, if the input is 3D this method will extract the
/// appropriate slice. Also, this method will convert the input to a
/// GDI+ natively supported type (ie. System::Byte).
///
///
private void ConvertInputToBitmap(int index)
{
// Create the list of scalar images
while (this.InputsAsScalar.Count < this.Inputs.Count)
this.InputsAsScalar.Add(null);
// Create the list of image slices
while (this.InputsAsSlice.Count < this.Inputs.Count)
this.InputsAsSlice.Add(null);
// Create the list of rescaled UC images
while (this.InputsAsRescaled.Count < this.Inputs.Count)
this.InputsAsRescaled.Add(null);
// Create the list of bitmaps
while (this.InputsAsBitmap.Count < this.Inputs.Count)
this.InputsAsBitmap.Add(null);
// Only proceed for images
if (!(this.Inputs[index] is itk.itkImageBase))
return;
// Update to renderable bitmap
this.ConvertInputToScalar(index);
this.ConvertScalarToSlice(index);
this.ConvertSliceToBitmap(index);
// Reset the requested region for the input
itk.itkImageBase input = this.InputAsImage(index);
input.RequestedRegion = input.LargestPossibleRegion;
}
private void ConvertInputToScalar(int index)
{
try
{
// Only proceed for images
if (!(this.Inputs[index] is itk.itkImageBase))
return;
// Get the input and scalar images
itk.itkImageBase input = this.InputAsImage(index);
itk.itkImageBase scalar = this.InputsAsScalar[index];
if (input.PixelType.IsScalar || input.PixelType.IsColor)
{
scalar = input;
}
else
{
// Create the new scalar image
scalar = itk.itkImage.New(new itk.itkPixelType(input.PixelType.TypeAsEnum), input.Dimension);
// Extract the index
itk.itkVectorIndexSelectionCastImageFilter filterSelect = itk.itkVectorIndexSelectionCastImageFilter.New(input, scalar);
filterSelect.RemoveAllObservers();
filterSelect.SetInput(input);
filterSelect.Index = this.ArrayIndex;
filterSelect.Update();
filterSelect.GetOutput(scalar);
input.DisconnectPipeline();
scalar.DisconnectPipeline();
filterSelect.Dispose();
}
// Set the scalar
this.InputsAsScalar[index] = scalar;
}
catch (Exception ex)
{
Trace.WriteLine("ERROR: Could not convert from input image to scalar image:");
Trace.WriteLine(ex.ToString());
}
}
private void ConvertScalarToSlice(int index)
{
try
{
// Only proceed for images
if (!(this.Inputs[index] is itk.itkImageBase))
return;
// Get the scalar and slice images
itk.itkImageBase scalar = this.InputsAsScalar[index];
itk.itkImageBase slice = this.InputsAsSlice[index];
if (scalar.Dimension == 2)
{
slice = scalar;
}
else if (scalar.Dimension == 3)
{
// Dispose of the old slice
if (slice != null && !slice.IsDisposed)
{
slice.Dispose();
slice = null;
}
// Create the new slice image
slice = itk.itkImage.New(scalar.PixelType, 2);
// Extract the slice
itk.itkExtractImageFilter filterExtract = itk.itkExtractImageFilter.New(scalar, slice);
filterExtract.RemoveAllObservers();
filterExtract.SetInput(scalar);
filterExtract.ExtractSlice(2, this.Slice);
filterExtract.Update();
filterExtract.GetOutput(slice);
slice.DisconnectPipeline();
filterExtract.Dispose();
}
// Set the slice
this.InputsAsSlice[index] = slice;
}
catch (Exception ex)
{
Trace.WriteLine("ERROR: Could not convert from scalar image to slice image:");
Trace.WriteLine(ex.ToString());
}
}
private void ConvertSliceToBitmap(int index)
{
try
{
// Only proceed for images
if (!(this.Inputs[index] is itk.itkImageBase))
return;
// Get the input and slice as images
itk.itkImageBase input = this.InputAsImage(index);
itk.itkImageBase scalar = this.InputsAsScalar[index];
itk.itkImageBase slice = this.InputsAsSlice[index];
itk.itkImageBase rescaled = this.InputsAsRescaled[index];
Bitmap bitmap = this.InputsAsBitmap[index];
// Dispose of the old bitmap
if (bitmap != null)
{
bitmap.Dispose();
bitmap = null;
}
// Handle the slice pixel type
if (slice.PixelType.TypeAsEnum != itk.itkPixelTypeEnum.UnsignedChar)
{
double minValue = 0.0;
double maxValue = 255.0;
// Get the min/max from either slice or compute from scalar
if (slice.Metadata.ContainsKey("MinimumValueAsD") &&
slice.Metadata.ContainsKey("MaximumValueAsD"))
{
// Get minimum and maximum values from slice
minValue = (double)slice.Metadata["MinimumValueAsD"];
maxValue = (double)slice.Metadata["MaximumValueAsD"];
}
else
{
// Check if we need to compute the min/max from scalar
if (!scalar.Metadata.ContainsKey("MinimumValueAsD") &&
!scalar.Metadata.ContainsKey("MaximumValueAsD"))
{
// Compute from scalar
itk.itkMinimumMaximumImageCalculator calcMinMax = itk.itkMinimumMaximumImageCalculator.New(scalar);
calcMinMax.SetImage(scalar);
calcMinMax.Compute();
scalar.Metadata.Add("MinimumValue", calcMinMax.Minimum);
scalar.Metadata.Add("MinimumValueAsD", calcMinMax.Minimum.ValueAsD);
scalar.Metadata.Add("MaximumValue", calcMinMax.Maximum);
scalar.Metadata.Add("MaximumValueAsD", calcMinMax.Maximum.ValueAsD);
scalar.DisconnectPipeline();
calcMinMax.Dispose();
}
// Get minimum and maximum values from the scalar
minValue = (double)scalar.Metadata["MinimumValueAsD"];
maxValue = (double)scalar.Metadata["MaximumValueAsD"];
}
// Rescale the slice for 8-bit display
rescaled = itk.itkImage.New(itk.itkPixelType.UC, 2);
itk.itkShiftScaleImageFilter filterShiftScale = itk.itkShiftScaleImageFilter.New(slice, rescaled);
filterShiftScale.RemoveAllObservers();
filterShiftScale.SetInput(slice);
filterShiftScale.Shift = -1 * minValue;
filterShiftScale.Scale = 1.0 / ((maxValue - minValue) / (itk.itkPixel_UC.NewMax().ValueAsD + 1.0));
filterShiftScale.Update();
filterShiftScale.GetOutput(rescaled);
slice.DisconnectPipeline();
rescaled.DisconnectPipeline();
filterShiftScale.Dispose();
}
else
{
rescaled = slice;
}
// Check the metadata for palette and format
string keyLookupTable = "LookupTable" + index.ToString();
int numComponents = 1;
ColorPalette palette = null;
PixelFormat format;
if (input.PixelType.ArrayAsEnum == itk.itkPixelArrayEnum.ArrayRGB)
{
format = PixelFormat.Format24bppRgb;
numComponents = 3;
}
else if (input.PixelType.ArrayAsEnum == itk.itkPixelArrayEnum.ArrayRGBA)
{
format = PixelFormat.Format32bppArgb;
numComponents = 4;
}
else if (input.Metadata.ContainsKey(keyLookupTable) &&
input.Metadata[keyLookupTable] is siGdiLookupTable)
{
// Get LUT
siGdiLookupTable lut = input.Metadata[keyLookupTable] as siGdiLookupTable;
// Use the palette and format
palette = lut.GetGdiPalette();
format = lut.GetGdiPixelFormat();
}
else
{
// Use the default palette and format
siGdiLookupTable lut = new siGdiLookupTable();
lut.SetTableRange(rescaled.PixelType);
lut.SetTableToGreyscale();
//lut.SetTableToHueBands(0.0, 1.0, 25);
//lut.SetTableRange(0.0, 255.0, 32);
//lut.SetTableToHueRange(0.0, 0.16666666);
//lut.SetTableToGreyscaleBands(25);
palette = lut.GetGdiPalette();
format = lut.GetGdiPixelFormat();
}
// Check if the stride is the same as the width
if (numComponents == 1 && rescaled.Size[0] % 4 == 0)
{
// Width = Stride AND scalar image: simply use the Bitmap constructor
bitmap = new Bitmap(rescaled.Size[0], // Width
rescaled.Size[1], // Height
rescaled.Size[0], // Stride
format, // PixelFormat
rescaled.Buffer // Buffer
);
}
else
{
unsafe
{
// Width != stride AND/OR RGB/A image: copy data from buffer to bitmap
int width = rescaled.Size[0];
int height = rescaled.Size[1];
byte* buffer = (byte*)rescaled.Buffer.ToPointer();
// Compute the stride
int stride = width;
if (rescaled.Size[0] % 4 != 0)
stride = ((rescaled.Size[0] / 4) * 4 + 4);
bitmap = new Bitmap(width, height, format);
Rectangle rect = new Rectangle(0, 0, width, height);
BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, format);
for (int k = 0; k < height; k++) // Row
{
byte* row = (byte*)bitmapData.Scan0 + (k * stride * numComponents);
for (int j = 0; j < width; j++) // Column
{
for (int i = 0; i < numComponents; i++) // Component
{
int swappedi = this.SwapArrayComponent(input.PixelType.ArrayAsEnum, numComponents, i);
row[(j * numComponents) + i] = buffer[(k * width * numComponents) + (j * numComponents) + swappedi];
}
}
}
bitmap.UnlockBits(bitmapData);
}
}
// Finish setting up the renderable bitmap
if (palette != null)
bitmap.Palette = palette;
this.InputsAsRescaled[index] = rescaled;
this.InputsAsBitmap[index] = bitmap;
}
catch (Exception ex)
{
Trace.WriteLine("ERROR: Could not convert from slice image to bitmap:");
Trace.WriteLine(ex.ToString());
}
}
///
/// Swaps the array component order for the GDI+ library.
///
///
///
///
///
private int SwapArrayComponent(itk.itkPixelArrayEnum enumArray, int numComponents, int component)
{
switch (enumArray)
{
case itk.itkPixelArrayEnum.ArrayRGBA:
switch (component)
{
case 0: return 2;
case 1: return 1;
case 2: return 0;
case 3: return 3;
default: return component;
}
default:
return (numComponents - component - 1);
}
}
///
/// Modify the ZoomFactor to zoom in x2.
///
public void ZoomInByFactor()
{
if (this.ZoomFactor >= 1.0) this.ZoomFactor += 0.5;
else this.ZoomFactor += 0.1;
if (this.ZoomFactor > 64) this.ZoomFactor = 64;
this.ZoomFactor = Math.Round(this.ZoomFactor, 6);
}
///
/// Modify the ZoomFactor to zoom out x2.
///
public void ZoomOutByFactor()
{
if (this.ZoomFactor > 1) this.ZoomFactor -= 0.5;
else this.ZoomFactor -= 0.1;
if (this.ZoomFactor < 0.1) this.ZoomFactor = 0.1;
this.ZoomFactor = Math.Round(this.ZoomFactor, 6);
}
//=====================================================================
#endregion
#region Point Coordinate Transform Helper Methods
//=====================================================================
///
/// Transform the given discrete point in screen space to physical image space.
///
///
///
public itk.itkPoint TransformScreenPointToImagePoint(Point screen)
{
// Convert the screen point to an image point (first 2 dimensions)
itk.itkImageBase input = this.InputAsImage(0);
itk.itkPoint point;
input.TransformIndexToPhysicalPoint(this.TransformScreenPointToImageIndex(screen), out point);
return point;
}
///
/// Transform the given discrete point in screen space to discrete pixel space.
///
///
///
public itk.itkIndex TransformScreenPointToImageIndex(Point screen)
{
// Convert the screen location to an image index
RectangleF rectImageScreen = (RectangleF)this.Metadata["ImageScreenRectangle"];
itk.itkImageBase input = this.InputAsImage(0);
itk.itkContinuousIndex cindex = new itk.itkContinuousIndex(input.Dimension);
cindex[0] = ((double)screen.X - (double)rectImageScreen.X) / this.ZoomFactor;
cindex[1] = (((double)screen.Y - (double)rectImageScreen.Y) / this.ZoomFactor) / (input.Spacing[1] / input.Spacing[0]);
// Set slice for 3-D images
if (cindex.Dimension == 3)
cindex[2] = (double)((int)this.Metadata["CurrentSlice"]);
// Adjust for index being in center of pixel
if (this.ZoomFactor > 1.0)
{
cindex[0] -= 0.5;
cindex[1] -= 0.5;
}
// Return
return cindex.ToIndex();
}
///
/// Transform a continuous index in pixel space to a continuous point in screen space.
/// This method assumes the upper-left corner of a pixel is the location.
///
/// The continuous index to convert.
///
public PointF TransformImageContinuousIndexToScreenPoint(itk.itkContinuousIndex cindex)
{
return this.TransformImageContinuousIndexToScreenPoint(cindex, false);
}
///
/// Transform a continuous index in pixel space to a continuous point in screen space.
///
/// The continuous index to convert.
/// If true, the location is adjusted to make the center of pixel the location.
///
public PointF TransformImageContinuousIndexToScreenPoint(itk.itkContinuousIndex cindex, bool makeCenterOfPixel)
{
// Convert the screen location to an image index
RectangleF rectImageScreen = (RectangleF)this.Metadata["ImageScreenRectangle"];
itk.itkImageBase input = this.InputAsImage(0);
PointF screenPoint = new PointF();
// Make the point the center of the pixel
if (makeCenterOfPixel)
for (int i = 0; i < cindex.Dimension; i++)
cindex[i] += 0.5;
// Convert from cidnex to screenPoint
screenPoint.X = (float)(((cindex[0]) * this.ZoomFactor) + (double)rectImageScreen.X);
screenPoint.Y = (float)(((cindex[1]) * this.ZoomFactor) * (input.Spacing[1] / input.Spacing[0]) + (double)rectImageScreen.Y);
return screenPoint;
}
///
/// Transform a discrete index in pixel space to a continuous point in screen space.
/// This method assumes the upper-left corner of a pixel is the location.
///
/// The index to convert.
///
public PointF TransformImageIndexToScreenPoint(itk.itkIndex index)
{
return this.TransformImageIndexToScreenPoint(index, false);
}
///
/// Transform a discrete index in pixel space to a continuous point in screen space.
///
/// The index to convert.
/// If true, the location is adjusted to make the center of pixel the location.
///
public PointF TransformImageIndexToScreenPoint(itk.itkIndex index, bool makeCenterOfPixel)
{
itk.itkContinuousIndex cindex = new itk.itkContinuousIndex(index);
return this.TransformImageContinuousIndexToScreenPoint(cindex, makeCenterOfPixel);
}
///
/// Transform a continuous point in physical space to a continuous point in screen space.
///
///
/// The point to convert.
///
public PointF TransformImagePointToScreenPoint(itk.itkImageBase image, itk.itkPoint point)
{
return this.TransformImagePointToScreenPoint(image, point, false);
}
///
/// Transform a continuous point in physical space to a continuous point in screen space.
///
///
/// The point to convert.
/// If true, the location is adjusted to make the center of pixel the location.
///
public PointF TransformImagePointToScreenPoint(itk.itkImageBase image, itk.itkPoint point, bool makeCenterOfPixel)
{
itk.itkContinuousIndex cindex;
image.TransformPhysicalPointToContinuousIndex(point, out cindex);
return this.TransformImageContinuousIndexToScreenPoint(cindex, makeCenterOfPixel);
}
//=====================================================================
#endregion
}
//=========================================================================
#endregion
}