Skip to main content
Skip table of contents

Custom Export File Formatting with String Templates

Overview

Users can create custom formatted export files in Custom Exporter using user-defined nfmt (Nano format) files.

This guide assume familiarity with Python f-string formatting. nfmt is an implementation of {fmt} C++ library. We’ve developed a custom syntax-highlighting extension for VS Code to aid in creating nfmt files. Contact support@nanotronics.ai for access to this custom VS extension.

Custom Exporter Usage

To use the custom string template formatter in Custom Exporter, you must select UserDefined as the Export Type.

image-20241021-180141.png

When selecting UserDefined export type, you must provide a nfmt file to format your export. You must point to this nfmt file using the field CustomTypeFile in the Export Setting nJson file.

Below is an example nJson file. Note that the CustomTypeFile is nanofmt.nfmt and the CustomTypeExtension is txt.

JSON
{
  "Custom Exporter": {
    "inputTablesNames": "src",
    "CustomTypeExtension": "txt",
    "CustomTypeFile": "nanofmt.nfmt",
    "Views" : [
      {
        "Title": "srcScanID",
        "Query": "SELECT ScanID FROM vwAnalysis WHERE AnalysisID = (SELECT AnalysisID FROM src LIMIT 1)"
      },
      {
        "Title": "vwSrcScan",
        "Query": "SELECT ScanID, Timestamp As StartTime, EndTime, (SELECT COUNT(*) FROM vwImages) AS NumImages , (SELECT PropertyData / 1000 FROM ScanProperties WHERE PropertyName = 'Scan Min X Microns')  AS 'X_mm', (SELECT PropertyData / 1000 FROM ScanProperties WHERE PropertyName = 'Scan Min Y Microns')  AS 'Y_mm', (SELECT PropertyData / 1000 FROM ScanProperties WHERE PropertyName = 'Scan Width Microns')  AS 'Width_mm', (SELECT PropertyData / 1000 FROM ScanProperties WHERE PropertyName = 'Scan Height Microns') AS 'Height_mm', Result, (SELECT PropertyData FROM ScanProperties WHERE PropertyName = 'SampleID') AS SampleName FROM Scans WHERE ScanID = (SELECT ScanID FROM srcScanID)"
      },
      {
        "Title": "vwSrcAnalysis",
        "Query": "SELECT AnalysisID, AnalyzerMenuText AS Analyzer, Timestamp AS StartTime, EndTime, NumDefects, AnalyzerName, Result FROM vwAnalysis WHERE AnalysisID = (SELECT AnalysisID FROM src LIMIT 1)"
      }
    ],
    "Queries": [
      {
        "Title": "scans",
        "Query": "SELECT * FROM Scans"
      }
    ]
  }
}

Example nfmt formatting file

Below is an example nfmt file. Anything not contained in braces { } is considered literal text, and brace characters can be escaped by doubling {{ }}.

Note that within the braces, the nfmt file references and accesses the SQL database queries made in the nJson Export Setting file, e.g. scans and vsSrcScan.

CPP
User-Defined Report Sample (from Query)
=======================================
Scans ID : {scans:e{"\t"}:ScanID}
Scans StartTime : {scans:e{"\t"}:Timestamp}
Scans EndTime : {scans:e{"\t"}:EndTime}
Scans Result : {scans:e{"\t"}:Result}
{db:sql{scan, SELECT * FROM vwSrcScan}:e{"\n"}:f{
"
Defined Report Sample (from views)
=======================================
Scan ID : {scan:ScanID}
Scan StartTime : {scan:StartTime}
Scan EndTime : {scan:EndTime}
Number of Images : {scan:NumImages}
X(mm) : {scan:X_mm}
Y(mm) : {scan:Y_mm}
Width(mm) : {scan:Width_mm}
Height(mm) : {scan:Height_mm}
Scan Result : {scan:Result}
SampleName : {scan:SampleName}
{db: 
sql{property, 
  f{"SELECT * FROM ScanProperties WHERE ScanID = {scan:ScanID}"} } 
  :e{"\n"}
  :f{"{property:PropertyName} : {property:PropertyData}"} }
  
{db:sql{analysis, SELECT * FROM vwAnalysis WHERE AnalysisID = (SELECT AnalysisID
FROM src LIMIT 1)}
  :e{"\n"}
  :f{
"
Analyzer
--------
StartTime : {analysis:Timestamp}
EndTime : {analysis:EndTime}
Defects : {analysis:NumDefects}
AnalyzerName : {analysis:AnalyzerName}
"} }

Devices
-------
{db:
sql{device, f{"SELECT * FROM vwDevices WHERE ScanID = {scan:ScanID} ORDER BY
DeviceID"} }
:e{"\n"}
:f{
"
DeviceID : {device:DeviceID}
X : {device:X}
Y : {device:Y}
width : {device:width}
height : {device:height}
Images
------
{db:sql{image, f{"
SELECT * FROM vwImages WHERE ImageID IN
(SELECT ImageID FROM DevicesInImages WHERE DeviceID = {device:DeviceID})
ORDER BY ImageID"} }
:e{"\n"}
:f{
"
ImageID : {image:ImageID}
  X : {image:X}
  Y : {image:Y}
  width : {image:wMicrons}
  height : {image:hMicrons}
  
Defects
-------
DefectID X Y W H Area
{db:sql{d, f{"SELECT * FROM src WHERE ImageID = {image:ImageID} ORDER BY DefectID"}
}
::e{" {d:DefectID:<11} {d:X:<6.5g} {d:Y:<6.5g} {d:W:<6.5g} {d:H:<6.5g}
{d:Area:<9.8g}\n"
} }"
} }"
} }"
} }

Example Export File

The following export file was generated via Custom Exporter using the example nfmt file above. The data is sample data, but typically the data draws from the nSpec scan database. Only a portion of the Defects have been included for brevity.

CODE
User-Defined Report Sample (from Query)
=======================================
Scans ID        : 1	2	3	4	5	6
Scans StartTime : 2024-09-08 22:45:10.624	2024-09-08 22:47:04.214	2024-09-08 22:48:05.204	2024-09-08 22:50:10.931	2024-09-08 22:51:09.385	2024-09-08 22:53:06.674
Scans EndTime   : 2024-09-08 22:47:03.977	2024-09-09 12:48:39.323	2024-09-08 22:50:08.973	2024-09-09 12:55:33.344	2024-09-08 22:53:04.566	2024-09-09 13:00:17.962
Scans Result    : Success	Success	Success	Success	Success	Success

Defined Report Sample (from views)
=======================================
Scan ID          : 6
Scan StartTime   : 2024-09-08 22:53:06.674
Scan EndTime     : 2024-09-09 13:00:17.962
Number of Images : 7982
X(mm)            : 15.254580095
Y(mm)            : 15.538067214
Width(mm)        : 310.29911172
Height(mm)       : 310.501818577
Scan Result      : Success
SampleName       : 300mm_Wafer
Alignment Angle : 0.789400
Alignment File : \\DESKTOP\Alignments\300mm_Wafer_Alignment.csv
Autofocus Set : 300mm_Wafer_AF
Autofocus Type : Automatic Predictive
Autoloader Set : None
CenterX : 170384.977373
CenterY : 170668.930430
DiePitchX : 64498.265301
DiePitchY : 90001.686035
Edge Exclude : 3000.000000
Exposure Time : 1
Filter Slider Status : None
FlatOffsetAngle : 0.000000
FlatSegmentLength : 0.000000
Focus Point Pattern : 300mm 9 Point
Focus Predictor : PreFoc Polynomial Standard
Golden Tile Number of Devices : 8
Golden Tile Scan : 1
Golden Tile Tiles per Device : 88
Illumination : Bright Field
Image Height Microns : 7410.389169
Image Height Pixels : 4268
Image Width Microns : 7888.396686
Image Width Pixels : 4398
Initiator : Manual
Install Build Version : 0.24.2.0
JobName : 300mm_Wafer_Demo_Job
JobUpdateTime : 2024-09-08 23:42:31.428
KLARFMode : 1
Layout File : D:\Scans\300mm_Wafer\Scan_006\Layout.csv
Light Intensity Control 1 : Setpoint
Light Intensity Control 2 : Setpoint
Light Intensity Control Value 1 : 0
Light Intensity Control Value 2 : 1000
Light Source 1 : Nano:Transmitted
Light Source 2 : Nano:Reflected
Mosaic Name : 300mm_Wafer_Mosaic.png
Objective : 2.5X - CFI L Plan 2.5X
OriginOffsetX : 0.000000
OriginOffsetY : 0.000000
Pattern : WholeStage
Pattern Type : 2
Radius : 149988.238372
SampleCenterX : 172401.665016
SampleCenterY : 172685.624342
SampleID : 300mm_Wafer
SampleLength : 300.000000
Scan Height Microns : 309401.818577
Scan Min X Microns : 16256.183319
Scan Min Y Microns : 16348.184435
Scan Tilt Angle : 0.000000
Scan Width Microns : 308409.111720
Stage Height Microns : 343179.700000
Stage Width Microns : 344681.400000
XOffset : -17537.000000
YOffset : -18344.000000

Analyzer
--------
  StartTime     : 2024-09-09 17:20:57.937
  EndTime       : 2024-09-09 17:21:31.650
  Defects       : 23189
  AnalyzerName  : nrad_rangeselect

Devices
-------
  DeviceID : 1
  X        : 28645.193836
  Y        : 117102.611014
  width    : 63391.820591
  height   : 77005.699667

  Images
  ------
  ImageID  : 2001
    X      : 28642.416191
    Y      : 117095.278032
    width  : 4461
    height : 4169

    Defects
    -------
      DefectID    X      Y      W      H      Area

  ImageID  : 2002
    X      : 36569.712877
    Y      : 117096.278032
    width  : 4461
    height : 4169

    Defects
    -------
      DefectID    X      Y      W      H      Area

  ImageID  : 2003
    X      : 44498.009564
    Y      : 117096.278032
    width  : 4461
    height : 4169

    Defects
    -------
      DefectID    X      Y      W      H      Area

ImageID  : 5750
    X      : 138607.349836
    Y      : 82548.104671
    width  : 4461
    height : 4169

    Defects
    -------
      DefectID    X      Y      W      H      Area
      97734       2134.8 2252.4 5.6254 3.9378 22.468003
      97735       1714.6 2124.7 6.0588 3.3427 20.88575 
      97736       534.98 2172.5 166.51 102.38 9194.793 
      97737       895    2134.8 32.057 1.636  24.050257
      97738       2349.7 2105   7.5578 8.3532 54.429529
      97739       547.35 2070.7 4.7733 4.7733 21.518651
      97740       895    2040.9 1.1251 37.127 24.050257
      97741       534.98 2045.9 166.51 93.381 6940.3979
      97742       1630.8 1974.5 1.1251 50.628 23.100905
      97743       296.46 1921.1 7.16   11.933 74.365926
      97744       895    1911.5 1.1251 50.628 35.126033
      97745       534.41 1919.4 165.95 101.82 9144.4774
      97746       895    1845.1 1.1251 57.941 33.54378 
      97747       232.89 1797.9 5.0629 6.1879 31.328624
      97748       1630.8 1856.4 1.1251 125.45 58.543389
      97749       534.41 1793.4 165.95 92.819 9597.6348
      97750       2462.8 1754.6 24.263 9.5844 164.23794

Basic Pieces of Syntax

We only add new syntax to the {fmt} library for format-specifiers; these are the parts of a {variable_name:format_spec} to the right of the first colon. There are a couple of basic ideas:

Joining format-specifiers

Instead of using a single block of characters from a : to a } to denote how to format some typed thing, we will allow a sequence of these to iteratively turn the thing into progressively easier-to-format things. This is not required in regular f-strings, which never permit a : to appear in a format-specifier.

Iterables

If you have some iterable variable containing things of type T, then it is formatted with a joiner to become something that is formatted with a format-specifier for something of type T. So for example, if we had scan_ids,was a list {1, 2, 3}, and it was formatted with {scan_ids:, :03}, that would produce the string 001, 002, 003.

Note that this is not quite the same as the range-based syntax extension described in the library, because we can add joiners.

Function calls

In some places, we embed something that should be read as a function call. Function calls are a name matching [A-Za-z_][A-Za-z_0-9]*, followed by a {}-wrapped, ,-delimited sequence of arguments. Whitespace before and after is ignored. For example:

image-20240729-151956.png

Would require foo and bar to be variables in the surrounding context, foo to understand how to find a function bar, and implicitly that function must return a floating point number to be correctly formatted by the regular format specifier:<6.5g.

Escaped f-strings

Anywhere in a format specifier that requires a string, we can recursively have an f-string. This is done with the reserved functions f and e. The basic syntax for this is to wrap in "" and {}:

image-20240729-150708.png

Note, critically, the space after the magenta-highlighted }. Some trailing whitespace after this function call is mandatory for distinguishing closing two scopes and intending to embed a raw } into the embedded f-string.

The function e has the same semantics, but additionally unescapes C-style \<char> characters. The main ones of use are \t, \r, \n, \0 (tab, carriage return, line feed, null). All but a null can technically be embedded in a string without escaping, but they can be hard to read. The following two lines express the same intent, but the latter is much clearer:

image-20240729-145946.png

For these embeddede and fstrings, whitespace between the { and " and " and } can be freely added. If whitespace between the { and " matches exactly with whitespace at the beginning or end of the quoted string, it is removed; this allows for clean syntax separating the management of a block of text to be embedded and the text to be embedded. For example:

image-20240729-150238.png

Embeds an f-string which starts with A and ends immediately after the AnalyzerName, without any trailing newline (for now, don’t worry about what theanalysis variable is).

SQL formatting

Formatting a DB

For now, a SqlDB exposes exactly one callable function, sql{row_name, sql_string}, which runs the query in sql_string and returns an iterable of rows, each of which will be exposed to subsequent formatting as a variable named row_name. For now, this means that to produce anything useful, the row must be formatted with some embedded f-string, since it is named.

Currently we are in the context of file exports for an analysis, there’s only one natural DB (the scanDB), which is exposed as a variable named db. As in other Exporters, the .njson file may have a number of queries to produce named views or other iterable rows that were returned by a SQL query.

Formatting a SQL row

Rows are dictionaries, indexed by their column titles. So something like:

image-20240729-161024.png

Will produce a list of the ScanIDs for every scan in the Scans table, separated with", ".

If a query is embedded in the .njson file, e.g. as

image-20240729-162648.png

then the entire iterable of results will be named scans, and so can be formatted like:

image-20240729-162738.png

This is structurally less flexible, but is simpler syntax in the .nfmtfile.

Note that in either case, the format specifier here is simply producing some integer that is then default formatted; if this needed to be controlled with a further specifier for width or padding that occurs exactly as for any other integer.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.