Introduction
This is a short introduction to a Physics related piece of software: the Grating Calculator program.
The Grating Calculator program is free software available on SourceForge under the GPL GNU License Version 3.0. The article presents the Physics problem that is solved by the software as well as the use under MFC of the VTK (Visualisation Tool Kit) Libraries as used by the program for 3D graphical representation of physical results. The VTK libs are a 3D rendering engine used in Scientific Visualisation built over the OpenGL graphics base libraries. The version used for the VTK libs is the 5.4.2 VTK Home. The program also makes use of the Charting Control of Cedric Moonen: Charting Control ver. 3.0, available on the CodeProject.
The problem addressed by the program is the following: concave specular (i.e., reflective) diffraction grating is used in research to obtain focal properties in conjunction with a diffractive element. The light reflected by such gratings is focused in addition to being spectrally dispersed. This makes possible to have a single element as the complete optical instrument that diffracts and focuses the light onto a plane or curved surface for recording. For instance, the curved Rowland gratings are the simplest optical elements that combine spectral diffraction with focusing properties. Gratings mounted in the so called Rowland mount (i.e., set-up) have the property to focus spectrally dispersed light in a circle whose diameter is the size of the radius of the concave grating. If the light source is on such a circle (called Rowland circle), then the light is focused onto the same circle after diffraction.
More recently, physicists have started using the so-called Flat Field Spectrometers. Such spectrometers use concave reflective gratings whose line density is not strictly periodical. The grooves on such gratings are ruled with a periodicity that changes from end to end in an aperiodical way. The ruling of such gratings is controlled numerically to adhere to such a prescribed pattern in a very precise way. Such aperiodicity superimposed on the main periodical ruling gives as a result the fact that such gratings are capable of focusing spectrally dispersed light onto surfaces approximately flat, i.e., plain. This is useful as most of the recording sensors nowadays available have flat surfaces (for instance, Charged Coupled Devices - CCDs - as opposed to pellicular films).
The program presented is capable of solving the ray-tracing equations for a general grating, giving the focus position for each dispersed light wavelength in the range sought. The grating is characterised in terms of the aperiodical ruling, and may go from the simple Rowland mount (no aperiodicity) to the most modern flat field ones (controlled aperiodical ruling). The program already recognizes in the set-up parameters two kinds of aperiodically ruled gratings as used in the modern research for soft x-ray spectrometers. The parameters are derived from the works of refs. [1,2,3], where the notion of current day grazing incidence flat field spectrometer is introduced (see also ref. [5]).
Disclaimer: The present article has a thick physical description of the problem solved by the program, and the only code presented is about the use of the VTK 3D libraries in displaying scientific data.
Physics Background
This section illustrates the physical problem solved by the program in physical terms.
Flat field spectrometers in the soft x-ray domain are a well established technology for spectroscopic studies. Concave and plane varied line-space (VLS) gratings providing enhanced spectral image focusing properties are used for synchrotron radiation instrumentation [1], plasma diagnosis [2], and space astrophysics [3]. In the design of a concave grating and in the analysis of its spectral image focusing properties, an analytical method that uses the light path function is usually adopted [4].
If point P is a generic point on the grating with local coordinates u, w, l on its surface (with u as a function of w, l), and A and B are, respectively, a point source and a nominal focal point of a spectrum of wavelength lambda diffracted in the mth order by the grating; then the light path function is defined as:
F = AP + BP + n*m*lambda
where n is the number of grooves at P counted from a reference point O on the grating (which we can assume is the centre of the grating blank).
The grating set-up for computations
According to Fermat's principle, for point B in the figure to be a stigmatic image of A at the wavelength lambda, it is required that the following differential conditions be satisfied simultaneously:
dF/dw = 0, dF/dl = 0.
With reference again to the figure, we introduce a polar coordinate system and express the distances as:
AP = r<sup>2</sup> + u<sup>2</sup> + l<sup>2</sup> - 2*u*r*cos(alpha) - 2*w*r*sin(alpha)
BP = r'<sup>2</sup> + u<sup>2</sup> + l<sup>2</sup> - 2*u*r'*cos(beta) - 2*w*r'*sin(beta)
where alpha is the angle of incidence, and beta of diffraction from the grating at O.
For a concave grating with a spherical substrate, a point P is defined by the equation:
(u-R)<sup>2</sup> + w<sup>2</sup> + l<sup>2</sup> = R<sup>2</sup>
where R is the radius of the grating. Expanding in a power series, we get:
u = (w<sup>2</sup>+l<sup>2</sup>)/2R + (w<sup>2</sup>+l<sup>2</sup>)<sup>2</sup>/8R<sup>3</sup> + ...
The groove number n on the varied line spaced grating (aperiodically ruled) is defined as:
n = (rho)<sub>0</sub> * ( w + b<sub>2</sub>/R * w<sup>2</sup> + b<sub>3</sub>/R<sup>2</sup> * w<sup>3</sup> + b<sub>4</sub>/R<sup>3</sup> * w<sup>4</sup> + ... )
where (rho)0 is the line density at the centre O of the grating (hence it is the periodical component of the line density), and b2, b3, and b4 are the controlling numerical parameters for the aperiodical part of the ruling of the grating.
Using the equations above, we can express the light path function as a power series of w and l, as follows:
F = r + r' + w*F<sub>10</sub> + w<sup>2</sup>*F<sub>20</sub> + l<sup>2</sup>*F<sub>02</sub>
+ w<sup>3</sup>*F<sub>30</sub> + w*l<sup>2</sup>*F<sub>12</sub> + w<sup>4</sup>*F<sub>40</sub>
+ w<sup>2</sup>*l<sup>2</sup>*F<sub>22</sub> + l<sup>4</sup>*F<sub>04</sub> + O(w<sup>5</sup>).
In this equation, every Fij term can be expressed as:
F<sub>ij</sub> = C<sub>ij</sub> + m*lambda*rho<sub>0</sub>*M<sub>ij</sub>
For a complete list of the coefficients Cij and Mij, see ref. [3].
When an aperiodically ruled grating is designed for an imaging spectrometer, the optical layout and the ruling parameters are chosen to minimise aberrations along both the spectral and spatial directions. Stigmatic imaging requires that Fij=0 to the highest possible order.
The condition F10=0 gives the grating equation:
sin(alpha) + sin(beta) = m*lambda*rho<sub>0</sub>
while the focal curve along the spectral dispersion direction is obtained from F20=0 to be used in conjunction with the grating equation:
r' = r * R * cos<sup>2</sup>(beta) /
( r*(cos(alpha)+cos(beta)-2*m*lambda*rho<sub>0</sub>*b<sub>2</sub>) - R*cos<sup>2</sup>(alpha))
This shows that only the linear ruling parameter b2 determines the position of the focal plane, given any optical layout. The two other ruling parameters b3, b4 are often chosen in order to minimise the coma and spherical aberration (F30=0, F40=0, respectively) in the spectral direction at a given wavelength of interest. Particular mountings can also be investigated in order to reduce the aberrations along the spatial direction [3].
To estimate the spectral focusing properties of these spectrometers, a ray-tracing code may be used instead of the equations for Fij=0.
The code determines the focus position by ray-tracing a large number of rays through the grating and following them until the smallest spread is found. For stigmatic imaging from a reflecting surface, the rays should converge to the focus after having travelled an equal amount of time from the source. This is true also for a grating if the optical path length introduced by its elements for constructive interference is taken into account. This is the core of the code: for each ray, the optical path length is corrected by an amount corresponding exactly at the diffraction introduced by the grating in the dispersed ray wavelength. For diffraction to the mth order, this amount corresponds to:
m*lambda*(Number of ruled lines between the point P on grating and reference point O)
I.e., exactly the term on the right of the optical light path function, as defined as:
F = AP + BP + n*m*lambda
As the image is not stigmatic, the smallest geometrical spread of the rays around the focus gives an estimate of the spectral resolution. This resolution that geometric optics predicts, of course, cannot exceed the wave optics limit. The code has been tested against the analytical formulae, and found in good agreement with it in predicting the location of the focus plane.
VTK Rendering in the Code
The most interesting piece of software for CodeProject readers contained in the program is the class that is used to interface the program with the 3D rendering library VTK. Such a class is reproduced here with comments about all its members, describing the use of the VTK 3D graphics library. In an MFC application using VTK, we derive a CView
type object from the class CVTKView
that itself derives from CView
. This allows seamless integration of the VTK rendering engine in an MFC view.
The header of the class follows:
#if !defined( __VTK_VIEW__ )
#define __VTK_VIEW__
#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkProperty.h"
#include "vtkProperty2D.h"
#include "vtkPropCollection.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkTextProperty.h"
#include "vtkMFCWindow.h"
#include "vtkWin32OpenGLRenderWindow.h"
class CVTKView : public CView
{
DECLARE_DYNCREATE(CVTKView)
protected:
CVTKView(); virtual ~CVTKView();
protected:
vtkRenderer* pvtkRenderer;
vtkMFCWindow* pvtkMFCWindow;
bool m_bInitCreate; bool m_bInitUpdate;
public:
vtkRenderer* GetRenderer() { ASSERT(pvtkRenderer); return pvtkRenderer; }
vtkMFCWindow* GetMFCWindow() { ASSERT(pvtkMFCWindow); return pvtkMFCWindow; };
void Render();
virtual void OnViewCreate();
virtual void OnViewInitUpdate();
public:
virtual void OnDraw(CDC* pDC); #ifdef _DEBUG
virtual void AssertValid() const;
#ifndef _WIN32_WCE
virtual void Dump(CDumpContext& dc) const;
#endif
#endif
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnSize(UINT nType, int cx, int cy);
virtual void OnInitialUpdate();
};
#endif // !defined( __VTK_VIEW__ )
The body of the class follows:
#include "stdafx.h"
#include "VTKView.h"
IMPLEMENT_DYNCREATE(CVTKView, CView)
CVTKView::CVTKView()
{
pvtkMFCWindow = NULL;
pvtkRenderer = NULL;
m_bInitCreate = false;
m_bInitUpdate = false;
}
CVTKView::~CVTKView()
{
}
BEGIN_MESSAGE_MAP(CVTKView, CView)
ON_WM_CREATE()
ON_WM_DESTROY()
ON_WM_ERASEBKGND()
ON_WM_SIZE()
END_MESSAGE_MAP()
void CVTKView::OnDraw(CDC* pDC)
{
CDocument* pDoc = GetDocument();
if (pvtkMFCWindow)
{
if (pDC->IsPrinting())
pvtkMFCWindow->DrawDC(pDC);
}
}
#ifdef _DEBUG
void CVTKView::AssertValid() const
{
CView::AssertValid();
}
#ifndef _WIN32_WCE
void CVTKView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
#endif
#endif //_DEBUG
int CVTKView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
OnViewCreate();
m_bInitCreate = true;
return 0;
}
void CVTKView::OnViewCreate()
{
if ( !m_bInitCreate )
{
if (pvtkMFCWindow)
{
delete pvtkMFCWindow;
pvtkMFCWindow = NULL;
}
pvtkMFCWindow = new vtkMFCWindow(this);
pvtkRenderer = vtkRenderer::New();
if (pvtkMFCWindow)
pvtkMFCWindow->GetRenderWindow()->AddRenderer(pvtkRenderer);
}
}
void CVTKView::OnDestroy()
{
CView::OnDestroy();
if (pvtkMFCWindow)
{
delete pvtkMFCWindow;
pvtkMFCWindow = NULL;
}
if (pvtkRenderer)
{
pvtkRenderer->Delete();
}
}
BOOL CVTKView::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
void CVTKView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if (pvtkMFCWindow)
pvtkMFCWindow->MoveWindow(0, 0, cx, cy);
}
void CVTKView::OnInitialUpdate()
{
CView::OnInitialUpdate();
OnViewInitUpdate();
Render();
m_bInitUpdate = true;
}
void CVTKView::OnViewInitUpdate()
{
if ( !m_bInitUpdate )
{
vtkWin32OpenGLRenderWindow* pRenderWindow = NULL;
pRenderWindow = pvtkMFCWindow->GetRenderWindow();
if (pRenderWindow)
{
pRenderWindow->GetInteractor()->Initialize();
pRenderWindow->GetInteractor()->Start();
}
}
}
void CVTKView::Render()
{
if (!pvtkMFCWindow)
return;
vtkWin32OpenGLRenderWindow* pRenderWindow = NULL;
pRenderWindow = pvtkMFCWindow->GetRenderWindow();
if (pRenderWindow)
{
pvtkRenderer->Render();
pRenderWindow->Render();
}
}
VTK works around the concept of a graphical pipeline. The data is processed, starting with source objects that are then filtered (i.e., processed) and finally mapped in an actor of the view.
Source objects -> Filters -> Mappers -> Actors -> Insertion of actors in the Scene
In the derived class from CVTKView
, a single method is then used to add the graphical pipeline that displays our data in the VTK derived view:
void CPlotFocalDensity::PlotData()
{
if ( !m_pFocalSpotObj )
return;
CWaitCursor wc;
int nX, nY; nX = nY = 0;
m_pFocalSpotObj->GetAttribute(FS_DENSITY_3D_NX)->Get_int(nX);
m_pFocalSpotObj->GetAttribute(FS_DENSITY_3D_NY)->Get_int(nY);
CAttribute_Generic *at, *atX, *atY;
at = atX = atY = NULL;
at = m_pFocalSpotObj->GetAttribute(FS_DENSITY_3D);
atX = m_pFocalSpotObj->GetAttribute(FS_DENSITY_3D_X);
atY = m_pFocalSpotObj->GetAttribute(FS_DENSITY_3D_Y);
size_t Naux;
at->GetSize(Naux);
vtkStructuredGrid* sgrid = vtkStructuredGrid::New();
vtkFloatArray* scalar = vtkFloatArray::New();
vtkPoints* points = vtkPoints::New();
double x, y, z;
for (size_t j=0; j<Naux; j++)
{
atX->GetAt(x,j);
atY->GetAt(y,j);
points->InsertPoint(j,x,y,0.0);
at->GetAt(z,j);
scalar->InsertValue(j,z);
}
sgrid->SetDimensions(nX,nY,1);
sgrid->SetPoints(points);
sgrid->GetPointData()->SetScalars(scalar);
vtkLookupTable* lut = vtkLookupTable::New();
lut->SetNumberOfTableValues(256);
lut->SetHueRange(0.0,0.667);
lut->Build();
vtkDataSetMapper* mapper = vtkDataSetMapper::New();
mapper->SetInput(sgrid);
mapper->SetLookupTable(lut);
double tmp[2];
sgrid->GetScalarRange(tmp);
mapper->SetScalarRange(tmp);
vtkContourFilter* contourFilter = vtkContourFilter::New();
contourFilter->SetInput(sgrid);
contourFilter->GenerateValues(20,tmp);
vtkPolyDataMapper* contourMapper = vtkPolyDataMapper::New();
contourMapper->SetInput(contourFilter->GetOutput());
sgrid->GetScalarRange(tmp);
contourMapper->SetScalarRange(tmp);
if ( carpet )
{
pvtkRenderer->RemoveActor(carpet);
Render();
}
carpet = vtkActor::New();
carpet->SetMapper(mapper);
if ( contour )
{
pvtkRenderer->RemoveActor(contour);
Render();
}
contour = vtkActor::New();
contour->SetMapper(contourMapper);
if ( scalarBar )
{
pvtkRenderer->RemoveActor(scalarBar);
Render();
}
scalarBar = vtkScalarBarActor::New();
scalarBar->SetLookupTable(lut);
scalarBar->SetTitle(_T("Focal Spot Density"));
scalarBar->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
scalarBar->GetPositionCoordinate()->SetValue(0.8,0.2);
pvtkRenderer->AddActor(carpet);
pvtkRenderer->AddActor(contour);
pvtkRenderer->AddActor(scalarBar);
pvtkRenderer->SetBackground(1.0,1.0,0.4);
pvtkRenderer->ResetCamera();
pvtkRenderer->GetActiveCamera()->Zoom(3.0);
pvtkRenderer->GetActiveCamera()->Elevation(45);
pvtkRenderer->GetActiveCamera()->Azimuth(30);
pvtkRenderer->GetActiveCamera()->Dolly(1.0);
pvtkRenderer->ResetCameraClippingRange();
Render();
}
Points of Interest
Example of using VTK 3D libraries under MFC Visual Studio 2008 SP1 built applications.
Bibliography
- [1] T. Harada, T. Kita, M. Itou, H. Taira, and A. Mikuni, Nucl. Instrum. Methods A 246, 272 (1986). M. Itou, T. Harada, and T. Kita, Appl. Opt. 28, 146 (1989).
- [2] T. Kita, T. Harada, N. Nakano, and H. Kuroda, Appl. Opt. 22, 512 (1983). N. Nakano, H. Kuroda, T. Kita, and T. Harada, Appl. Opt. 23, 2386 (1984).
- [3] T. Harada, H. Sakuma, K. Takahashi, T. Watanabe, H. Hara, and T. Kita, Appl. Opt. 37, 6803 (1998).
- [4] T. Namioka, J. Opt. Soc. Am. 49, 446 (1959).
- [5] Instruments division, Hitachi Ltd, 882 Ichige, Hitachinaka-shi, Ibaragi 312− 0033, Japan.
History
- 27 October 2010 - First draft.