Writing your first application definition¶
This tutorial will guide you through writing your first valid NeXus application definition.
What should you should know before this tutorial?¶
- You should have a basic understanding of NeXus: see Learn > NeXus -> A primer on NeXus
- You should have
pynxtoolsinstalled: see the Installation guide
Note
For this tutorial, we will use the nyaml tool (developed by FAIRmat) that lets you write the NXDL definition in YAML and convert it to NXDL XML.
You can learn more in the nyaml documentation.
What you will know at the end of this tutorial?¶
You will know
- how to write a basic application definition
- how to validate the result with pynxtools
- how to add your new definition to
pynxtools
You will understand
- the structure of NXDL files
- the role of base classes and application definitions
- NeXus naming rules
- optionality and dimensions in NXDL
Goals¶
We want to build an application definition NXdouble_slit from scratch — a minimal but complete NeXus application definition for a classic optics experiment.
The experiment¶
In a double-slit experiment a coherent light source illuminates a barrier with two narrow slits. The diffracted waves interfere and produce a characteristic fringe pattern on a detector screen. Standard analysis extracts fringe spacing (→ wavelength) and envelope width (→ coherence length).
The data we need to record:
| Quantity | Why |
|---|---|
| Source wavelength | Determines fringe spacing |
| Slit width, slit separation | Determines diffraction envelope |
| Detector distance and pixel layout | Maps pixels to angles |
| 2D intensity array | The measurement itself |
Steps¶
1. Start with the skeleton¶
Every application definition is an XML file following the NXDL schema. Create NXdouble_slit.nxdl.xml with the following minimal content:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="nxdlformat.xsl"?>
<definition xmlns="http://definition.nexusformat.org/nxdl/3.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
category="application"
type="group"
name="NXdouble_slit"
extends="NXobject"
xsi:schemaLocation="http://definition.nexusformat.org/nxdl/3.1 ../nxdl.xsd">
<doc>Application definition for a double-slit interference experiment.</doc>
<group type="NXentry">
<field name="definition">
<enumeration><item value="NXdouble_slit"/></enumeration>
</field>
<field name="title"/>
<field name="start_time" type="NX_DATE_TIME"/>
</group>
</definition>
If you have pynxtools installed (see Installation Guide), add the NXDL XML file under src/pynxtools/definitions/contributed_definitions and validate that pynxtools can read it:
You should see a JSON template listing paths like /ENTRY[entry]/title and /ENTRY[entry]/start_time.
Concept paths vs. instance paths
The template uses concept names (ENTRY, INSTRUMENT) and instance names in brackets ([entry]). The bracketed name is the literal group name written to the HDF5 file; the upper-case name is the name of the NeXus concept. Read more in
Learn > ... > Rules for storing data in NeXus.
The definition field with an <enumeration> is a convention — it locks the field to the name of the application definition so that readers and validators can identify the file format unambiguously.
Working with nyaml¶
From now on, you will exclusively develop this application definition in YAML. As outlined above, there exists a tool nyaml (developed by FAIRmat) that lets you write the NXDL definition in YAML and convert it to NXDL XML.
Install the nyaml Python package:
This installs a CLI command called nyaml2nxdl (or short n2n). Use this to convert your NXDL XML file to YAML:
Your basic application definition should look like this is the YAML format:
category: application
doc: |
Application definition for a double-slit interference experiment.
type: group
NXdouble_slit(NXobject):
(NXentry):
definition:
enumeration: [NXdouble_slit]
title:
start_time(NX_DATE_TIME):
Now we can start adding components to the experiment model. Conceptually, the experiment will be structured roughly as follows:
NXentry
├─ NXinstrument
│ ├─ NXsource
│ ├─ NXslit
│ └─ NXdetector
│ └─ NXdata (measurement data)
└─ NXdata (default plot)
NXentry represents a single measurement, NXinstrument describes the experimental setup, and NXdata defines the default plottable dataset.
2. Add the instrument¶
Nest the physical components inside NXinstrument. Start with the light source:
(NXinstrument):
source(NXsource):
wavelength(NX_FLOAT):
unit: NX_WAVELENGTH
doc: |
Central wavelength of the light source.
Unit categories
unit: "NX_WAVELENGTH" is a unit category, not a unit. It declares that the field stores a wavelength-equivalent quantity (nm, Å, µm, …). The actual unit chosen by the writer is stored as a sibling HDF5 attribute wavelength/@units. You should always try to use one of the existing unit categories (see NeXus manual > Unit Categories). If none of these apply, you may use a raw string like "eV/mm" as an example. In this example, the instance units must match the dimension of energy over length.
3. Choose the right base class for the slit¶
Browse the base class catalogue in NeXus manual > ... > Base classes. You will find:
NXaperture— generic apertureNXslit— specific to slit-type apertures, withx_gapandy_gapalready defined
Always prefer the most specific class. Add it with an instance name that describes the physical object:
double_slit(NXslit):
x_gap(NX_FLOAT):
unit: NX_LENGTH
doc: |
Width of each individual slit.
slit_separation(NX_FLOAT):
unit: NX_LENGTH
doc: |
Center-to-center distance between the two slits.
height(NX_FLOAT):
unit: NX_LENGTH
exists: optional
material(NX_CHAR):
exists: optional
Instance name vs. concept name
double_slit is the concept name — the name of the concept as defined in the NeXus definitions. NXslit is the associated base class. By default (nameType="specified") this exact literal string is required in every conforming HDF5 file. You can relax this with nameType:"any" to accept any valid name. See
Learn > ... > Rules for storing data in NeXus > Name resolution.
4. Add the detector with dimensions¶
The detector produces a 2D array. Use <symbols> to name the array dimensions and reference them in <dimensions>.
Add this block at the top, outside of the class NXslit:
symbols:
doc: |
Dimension symbols used in this definition.
n_x: |
Number of detector pixels along x.
n_y: |
Number of detector pixels along y.
Using symbolic names (n_x, n_y) instead of hardcoded integers makes the definition self-documenting and allows validation tools to verify dimensional consistency across fields.
Then add the detector group inside NXinstrument:
detector(NXdetector):
distance(NX_FLOAT):
unit: NX_LENGTH
doc: |
Distance from the slit plane to the detector surface.
data(NXdata):
doc: |
Raw 2D pixel data collected by the detector with no calibration
applied. This group stores data at the lowest level of processing
possible, indexed by integer pixel coordinates.
\@signal:
enumeration: [data]
\@axes:
enumeration: [['x', 'y']]
data(NX_NUMBER):
exists: recommended
unit: NX_ANY
doc: |
Raw 2D intensity array indexed by pixel position.
dimensions:
rank: 2
dim: (n_x, n_y)
x(NX_INT):
doc: |
Pixel indices along the horizontal detector axis (0-based).
dimensions:
rank: 1
dim: (n_x,)
y(NX_INT):
doc: |
Pixel indices along the vertical detector axis (0-based).
dimensions:
rank: 1
dim: (n_y,)
Raw data first, processed data separately
Always store the rawest data you have. Here the detector axes are integer pixel indices (x, y), i.e., exactly what the hardware records. The calibrated view with physical spatial offsets (mm, µm) belongs in a separate NXdata group at the NXentry level, wired as the default plot (step 6 below). An optional NXprocess group can document the conversion between the two
(step 5 below).
5. Add an optional processing group¶
Three levels express how important a concept is for conformance:
| Level | NXDL | nyaml | What validators do |
|---|---|---|---|
| Required | required="true" |
required: true |
Fail/warn if absent |
| Recommended | recommended="true" |
recommended: true |
Warn if absent or accept |
| Optional | optional="true" |
optional: true |
Silently accept if absent |
Note that by default, every concept in a base class is optional (even if none of these keys is written), whereas in application definition, every concept is required, unless it is marked recommended or optional.
The pixel offsets above are recommended — essential for calibrated analysis but not always stored. Use optional for supplementary metadata like material.
Tip
When in doubt, lean towards recommended over required. A definition that is too strict discourages adoption; a definition that is too loose loses its interoperability value.
Flexible naming with nameType¶
Names in both NXDL files and the files that instantiate the schema follow a particular naming logic. There's the possibility to define concept names that are different to the names of the actual data instances. You can learn more about the naming rules in Learn > ... > Rules for storing data in NeXus. In summary, NeXus names are defined by a combination of the nameType attribute and whether (parts of) the names are lower- or uppercase.
By default every named group, field, or attribute in NXDL requires an exact name in the HDF5 file (nameType="specified"). NeXus provides two relaxed alternatives:
nameType |
nyaml example | What it means |
|---|---|---|
specified (default) |
double_slit(NXslit): |
Exactly the literal name double_slit |
any |
(NXinstrument): |
Any valid HDF5 group name is accepted |
partial |
processID(NXprocess): |
The ID suffix is a placeholder; any string can replace it |
The (NXinstrument): syntax (parentheses only, no name) that you already wrote is the nameType="any" shorthand — any instrument group name is valid.
nameType="partial" is useful when you expect multiple concepts of the same type but want to give each a meaningful name. The uppercase sequence ID marks the variable part; everything before it is the fixed prefix. For example, processID would match processing_pixel_cal, processing01, etc.
Add an optional processing group at the (NXentry) level (sibling of (NXinstrument)) to document how raw pixel data were converted to calibrated spatial offsets:
processID(NXprocess):
nameType: partial
exists: optional
doc: |
Describes one step in the processing chain that converts raw detector pixel data to the calibrated interference pattern stored in ``interference_pattern``. The 'ID' suffix in the group name is replaced by a short identifier chosen by the writer, e.g. 'pixel_calibration' or 'background_correction'. Multiple NXprocess groups may be present; their order is given by sequence_index.
sequence_index(NX_INT):
doc: |
Sequence index of processing, for determining the order of multiple
NXprocess steps. Starts with 1.
description(NX_CHAR):
doc: Free-text description of what this step does.
program(NX_CHAR):
exists: optional
doc: Version string of the software.
version(NX_CHAR):
exists: optional
date(NX_DATE_TIME):
exists: optional
6. Wire up NXdata¶
NXdata marks the default plot. Tools like NOMAD use the @signal and @axes attributes to render data without requiring user configuration. Add it as a sibling of NXinstrument (and processID):
interference_pattern(NXdata):
doc: |
Default plot: the calibrated 2D interference pattern with spatial axes. The signal and axes may be linked to the raw detector arrays or derived from it via one or more NXprocess steps.
\@signal:
enumeration: [data]
\@axes:
enumeration: [['x_offset', 'y_offset']]
data(NX_NUMBER):
unit: NX_ANY
doc: |
2D interference intensity after any processing steps.
dimensions:
rank: 2
dim: (n_x, n_y)
x_offset(NX_FLOAT):
unit: NX_LENGTH
doc: |
Horizontal spatial offset from the detector centre, derived from
pixel index and pixel pitch.
dimensions:
rank: 1
dim: (n_x,)
y_offset(NX_FLOAT):
unit: NX_LENGTH
doc: |
Vertical spatial offset from the detector centre, derived from
pixel index and pixel pitch.
dimensions:
rank: 1
dim: (n_y,)
The axes here are physical lengths (NX_LENGTH), not pixel indices — they represent where each pixel falls in real space after calibration.
Note
In a real HDF5 file, data and the axis fields in NXdata could be HDF5 links pointing to the detector group — not duplicated data. The NXDL defines what must be accessible at that path; the writer decides whether to copy or link.
7. Validate¶
Now that we are done, we can convert back to NXDL XML:
Run generate-template one final time and check that all required paths are listed:
Write a minimal HDF5 test file filling all required fields, then validate:
See How-tos > pynxtools > Validate NeXus files for details on interpreting the output.
The complete definition¶
You can find both NXdouble_slit.yaml and NXdouble_slit.nxdl.xml in the pynxtools examples:
NXdouble_slit.yaml (full)
category: application
doc: |
Application definition for a double-slit interference experiment.
Records the light source, aperture geometry, detector layout, and the
measured 2D interference pattern needed to determine fringe spacing and
source coherence length.
See https://en.wikipedia.org/wiki/Double-slit_experiment.
symbols:
doc: |
Dimension symbols used in this definition.
n_x: |
Number of detector pixels along x.
n_y: |
Number of detector pixels along y.
type: group
NXdouble_slit(NXobject):
(NXentry):
definition:
enumeration: [NXdouble_slit]
title:
start_time(NX_DATE_TIME):
doc: |
ISO 8601 datetime of the measurement start.
end_time(NX_DATE_TIME):
exists: recommended
(NXinstrument):
source(NXsource):
wavelength(NX_FLOAT):
unit: NX_WAVELENGTH
doc: |
Central wavelength of the light source.
coherence_length(NX_FLOAT):
unit: NX_LENGTH
exists: recommended
doc: |
Temporal coherence length of the source.
type(NX_CHAR):
exists: optional
enumeration: [Laser, Filtered lamp, LED]
double_slit(NXslit):
x_gap(NX_FLOAT):
unit: NX_LENGTH
doc: |
Width of each individual slit.
slit_separation(NX_FLOAT):
unit: NX_LENGTH
doc: |
Center-to-center distance between the two slits.
height(NX_FLOAT):
unit: NX_LENGTH
exists: optional
material(NX_CHAR):
exists: optional
detector(NXdetector):
distance(NX_FLOAT):
unit: NX_LENGTH
doc: |
Distance from the slit plane to the detector surface.
data(NXdata):
doc: |
Raw 2D pixel data collected by the detector with no calibration
applied. This group stores data at the lowest level of processing
possible, indexed by integer pixel coordinates.
\@signal:
enumeration: [data]
\@axes:
enumeration: [['x', 'y']]
data(NX_NUMBER):
exists: recommended
unit: NX_ANY
doc: |
Raw 2D intensity array indexed by pixel position.
dimensions:
rank: 2
dim: (n_x, n_y)
x(NX_INT):
doc: |
Pixel indices along the horizontal detector axis (0-based).
dimensions:
rank: 1
dim: (n_x,)
y(NX_INT):
doc: |
Pixel indices along the vertical detector axis (0-based).
dimensions:
rank: 1
dim: (n_y,)
processID(NXprocess):
nameType: partial
exists: optional
doc: |
Describes one step in the processing chain that converts raw detector
pixel data to the calibrated interference pattern stored in
``interference_pattern``. The 'ID' suffix in the group name is replaced
by a short identifier chosen by the writer, e.g. 'pixel_calibration'
or 'background_correction'. Multiple NXprocess groups may be present;
their order is given by sequence_index.
sequence_index(NX_POSINT):
doc: |
Sequence index of processing, for determining the order of multiple
NXprocess steps. Starts with 1.
description(NX_CHAR):
doc: |
Free-text description of what this processing step does.
program(NX_CHAR):
exists: optional
doc: Version string of the software.
version(NX_CHAR):
exists: optional
date(NX_DATE_TIME):
exists: optional
interference_pattern(NXdata):
doc: |
Default plot: the calibrated 2D interference pattern with spatial
axes. The signal data may be identical to the raw detector array or
derived from it via one or more NXprocess steps.
\@signal:
enumeration: [data]
\@axes:
enumeration: [['x_offset', 'y_offset']]
data(NX_NUMBER):
unit: NX_ANY
doc: |
2D interference intensity after any processing steps.
dimensions:
rank: 2
dim: (n_x, n_y)
x_offset(NX_FLOAT):
unit: NX_LENGTH
doc: |
Horizontal spatial offset from the detector centre, derived from
pixel index and pixel pitch.
dimensions:
rank: 1
dim: (n_x,)
y_offset(NX_FLOAT):
unit: NX_LENGTH
doc: |
Vertical spatial offset from the detector centre, derived from
pixel index and pixel pitch.
dimensions:
rank: 1
dim: (n_y,)
NXdouble_slit.nxdl.xml (full)
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="nxdlformat.xsl"?>
<!--
# NeXus - Neutron and X-ray Common Data Format
#
# Copyright (C) 2026-2026 NeXus International Advisory Committee (NIAC)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# For further information, see http://www.nexusformat.org
-->
<definition xmlns="http://definition.nexusformat.org/nxdl/3.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" category="application" type="group" name="NXdouble_slit" extends="NXobject" xsi:schemaLocation="http://definition.nexusformat.org/nxdl/3.1 ../nxdl.xsd">
<symbols>
<doc>
Dimension symbols used in this definition.
</doc>
<symbol name="n_x">
<doc>
Number of detector pixels along x.
</doc>
</symbol>
<symbol name="n_y">
<doc>
Number of detector pixels along y.
</doc>
</symbol>
</symbols>
<doc>
Application definition for a double-slit interference experiment.
Records the light source, aperture geometry, detector layout, and the
measured 2D interference pattern needed to determine fringe spacing and
source coherence length.
See https://en.wikipedia.org/wiki/Double-slit_experiment.
</doc>
<group type="NXentry">
<field name="definition">
<enumeration>
<item value="NXdouble_slit"/>
</enumeration>
</field>
<field name="title"/>
<field name="start_time" type="NX_DATE_TIME">
<doc>
ISO 8601 datetime of the measurement start.
</doc>
</field>
<field name="end_time" type="NX_DATE_TIME" recommended="true"/>
<group type="NXinstrument">
<group name="source" type="NXsource">
<field name="wavelength" type="NX_FLOAT" units="NX_WAVELENGTH">
<doc>
Central wavelength of the light source.
</doc>
</field>
<field name="coherence_length" type="NX_FLOAT" units="NX_LENGTH" recommended="true">
<doc>
Temporal coherence length of the source.
</doc>
</field>
<field name="type" type="NX_CHAR" optional="true">
<enumeration>
<item value="Laser"/>
<item value="Filtered lamp"/>
<item value="LED"/>
</enumeration>
</field>
</group>
<group name="double_slit" type="NXslit">
<field name="x_gap" type="NX_FLOAT" units="NX_LENGTH">
<doc>
Width of each individual slit.
</doc>
</field>
<field name="slit_separation" type="NX_FLOAT" units="NX_LENGTH">
<doc>
Center-to-center distance between the two slits.
</doc>
</field>
<field name="height" type="NX_FLOAT" units="NX_LENGTH" optional="true"/>
<field name="material" type="NX_CHAR" optional="true"/>
</group>
<group name="detector" type="NXdetector">
<field name="distance" type="NX_FLOAT" units="NX_LENGTH">
<doc>
Distance from the slit plane to the detector surface.
</doc>
</field>
<group name="data" type="NXdata">
<doc>
Raw 2D pixel data collected by the detector with no calibration
applied. This group stores data at the lowest level of processing
possible, indexed by integer pixel coordinates.
</doc>
<attribute name="signal">
<enumeration>
<item value="data"/>
</enumeration>
</attribute>
<attribute name="axes">
<enumeration>
<item value="['x', 'y']"/>
</enumeration>
</attribute>
<field name="data" type="NX_NUMBER" recommended="true" units="NX_ANY">
<doc>
Raw 2D intensity array indexed by pixel position.
</doc>
<dimensions rank="2">
<dim index="1" value="n_x"/>
<dim index="2" value="n_y"/>
</dimensions>
</field>
<field name="x" type="NX_INT">
<doc>
Pixel indices along the horizontal detector axis (0-based).
</doc>
<dimensions rank="1">
<dim index="1" value="n_x"/>
</dimensions>
</field>
<field name="y" type="NX_INT">
<doc>
Pixel indices along the vertical detector axis (0-based).
</doc>
<dimensions rank="1">
<dim index="1" value="n_y"/>
</dimensions>
</field>
</group>
</group>
</group>
<group name="processID" type="NXprocess" nameType="partial" optional="true">
<doc>
Describes one step in the processing chain that converts raw detector
pixel data to the calibrated interference pattern stored in
``interference_pattern``. The 'ID' suffix in the group name is replaced
by a short identifier chosen by the writer, e.g. 'pixel_calibration'
or 'background_correction'. Multiple NXprocess groups may be present;
their order is given by sequence_index.
</doc>
<field name="sequence_index" type="NX_POSINT">
<doc>
Sequence index of processing, for determining the order of multiple
NXprocess steps. Starts with 1.
</doc>
</field>
<field name="description" type="NX_CHAR">
<doc>
Free-text description of what this processing step does.
</doc>
</field>
<field name="program" type="NX_CHAR" optional="true">
<doc>
Version string of the software.
</doc>
</field>
<field name="version" type="NX_CHAR" optional="true"/>
<field name="date" type="NX_DATE_TIME" optional="true"/>
</group>
<group name="interference_pattern" type="NXdata">
<doc>
Default plot: the calibrated 2D interference pattern with spatial
axes. The signal data may be identical to the raw detector array or
derived from it via one or more NXprocess steps.
</doc>
<attribute name="signal">
<enumeration>
<item value="data"/>
</enumeration>
</attribute>
<attribute name="axes">
<enumeration>
<item value="['x_offset', 'y_offset']"/>
</enumeration>
</attribute>
<field name="data" type="NX_NUMBER" units="NX_ANY">
<doc>
2D interference intensity after any processing steps.
</doc>
<dimensions rank="2">
<dim index="1" value="n_x"/>
<dim index="2" value="n_y"/>
</dimensions>
</field>
<field name="x_offset" type="NX_FLOAT" units="NX_LENGTH">
<doc>
Horizontal spatial offset from the detector centre, derived from
pixel index and pixel pitch.
</doc>
<dimensions rank="1">
<dim index="1" value="n_x"/>
</dimensions>
</field>
<field name="y_offset" type="NX_FLOAT" units="NX_LENGTH">
<doc>
Vertical spatial offset from the detector centre, derived from
pixel index and pixel pitch.
</doc>
<dimensions rank="1">
<dim index="1" value="n_y"/>
</dimensions>
</field>
</group>
</group>
</definition>
Advanced: adding a new base class¶
In Nexus, you can not only compose existing base classes into application definitions, but also define your own base class. For this tutorial, we will create a new base class NXlaser that is a specialization of NXsource.
In our full application definition, we have an NXsource inside NXinstrument:
NXdouble_slit(NXobject):
(NXentry):
(NXinstrument):
source(NXsource):
wavelength(NX_FLOAT):
unit: NX_WAVELENGTH
doc: |
Central wavelength of the light source.
coherence_length(NX_FLOAT):
unit: NX_LENGTH
exists: recommended
doc: |
Temporal coherence length of the source.
type(NX_CHAR):
exists: optional
enumeration: [Laser]
Note that:
- the wavelength field is already defined in the NXsource base class. We are just redefining its documentation
- we added here the coherence_length which is special for a laser, but not defined for all sources
- we narrowed down the possible values for the type field from all available options in NXsource/type to just "Laser". This specialization constrains the source to lasers.
All of this is possible and valid NXDL syntax. However, many experiments in NeXus may use lasers. In that case, it makes sense to specialize the NXsource class and create a new NXlaser base class.
Create a new YAML file called NXlaser.yaml:
category: base
doc: |
A specialisation of NXsource for coherent laser sources.
Extends NXsource by adding a temporal coherence_length field and
restricting the inherited type field to the single value "Laser".
Use this base class in application definitions that require a laser
as the illumination source.
type: group
NXlaser(NXsource):
wavelength(NX_FLOAT):
doc: |
Central wavelength of the laser line.
coherence_length(NX_FLOAT):
unit: NX_LENGTH
exists: recommended
doc: |
Temporal coherence length of the source.
Determines the maximum path-length difference for which
interference fringes are visible.
type(NX_CHAR):
enumeration: [Laser]
Note
You do not need to redefine the units of the wavelength field here. In general, only those concepts that are either not defined in the inherited class or are overwritten shall be defined in the child class.
The complete NXlaser.yaml and NXlaser.nxdl.xml are in the pynxtools examples:
You can then use this new base class NXlaser in NXdouble_slit:
NXdouble_slit(NXobject):
(NXentry):
(NXinstrument):
source(NXlaser):
wavelength(NX_FLOAT):
coherence_length(NX_FLOAT):
exists: recommended
Here, you only need to define whether any of the fields in NXlaser are required or recommended. All fields in base classes are optional; in the application definition, all of them are inherited. If we want to require or recommend any of them, we have to explicitly say so.
Convert both NXlaser and NXdouble_slit back to NXDL XML:
nyaml2nxdl NXlaser.yaml --output-file NXlaser.nxdl.xml
nyaml2nxdl NXdouble_slit.yaml --output-file NXdouble_slit.nxdl.xml
Because you now use the class NXlaser, you should see paths like /ENTRY[entry]/LASER/wavelength and /ENTRY[entry]/LASER/coherence_length in the generated JSON template.
Add your definition to pynxtools¶
There are two paths depending on whether you want a local prototype or a community contribution.
Option A — Local development (fastest)¶
Copy both NXDL files directly into the contributed_definitions/ folder of your local pynxtools install or working tree:
cp NXlaser.nxdl.xml src/pynxtools/definitions/contributed_definitions/
cp NXdouble_slit.nxdl.xml src/pynxtools/definitions/contributed_definitions/
Verify that pynxtools picks them up:
This change lives only in your local checkout. It is useful for iterating quickly before submitting upstream.
Option B — Community contribution (permanent)¶
When your definition is stable, contribute it to the shared NeXus definitions repository so that all pynxtools users can benefit:
- Fork FAIRmat-NFDI/nexus_definitions on GitHub.
- Add your NXDL files under
contributed_definitions/(new definitions) orapplications/(promoted application definitions). - Open a pull request. The FAIRmat team will review the definition.
-
Once merged, update the
pynxtoolsdefinitions submodule to bring the new definition in:This runs
git submodule update --remoteon the definitions submodule and records the new commit hash in pynxtools.
Note
For very early-stage or instrument-specific definitions that are not yet ready for the community repository, Option A is the right choice. The contributed_definitions/ path is scanned automatically by pynxtools — no code changes are needed.
Option C — Standardization with the NIAC¶
When you use option B, your new definition will only be part of the FAIRmat NeXus definitions. Once your application definition or base class has gained sufficient approval from the community, it is possible to submit it to the NeXus International Advisory Committee (NIAC) for standardization. If approved, the new application definitions and base classes get eventually promoted to applications/ or base classes, respectively.
Next steps¶
- How-tos > NeXus > Write an application definition (how-to) — quick reference for experienced users
- Tutorial > Build a pynxtools reader — write a reader that produces files conforming to
NXdouble_slit - Contribute to FAIRmat NeXus definitions