In order to use SpecUtils from node.js, we need to compile the C++ into a node module, but once this is done, you can then parse spectrum files, access all spectral and meta data, and convert the file to a different format within JavaScript.
Pre-built macOS binaries are unsigned. macOS will quarantine downloaded files and block the native module from loading. To fix this, run the following from the extracted directory:
xattr -r -d com.apple.quarantine .To create the add-on we use cmake-js to build the SpecUtils library as well as some node-addon-api interface code to allow accessing the C++ functions from JavaScript. Once the module is built, it should work with any node.js versions >=6.14.2 thanks to the ABI stability offered by n-api.
You will need a reasonable recent version of CMake (version >=3.5) installed, a C++ compiler that can handle C++11. You will of course also need node.js installed.
- Windows: You will need the CMake and node executables in your PATH variable. When installing node.js there may be an option to install the MSVC command line tools through Chocolatey, otherwise you will need to have MSVS-2012 or newer installed (development done with MSVS-2017). Other compilers (clang, MingGW) should work, but have not been tested. This project links against the static runtime libraries (e.g., uses the '/MT' flag) so the resulting module is self contained. When executing the steps below to compile the code you will need to open the 'x64 Native Tools Command Prompt' provided by MSVS, or execute the appropriate vcvarsall.bat.
- macOS: You will need the Xcode command line tools installed, which can be done by running
xcode-select --install. CMake, and node, which can be installed using MacPorts, HomeBrew, or manually. - Linux: You can use your package manager to install node.js, CMake, and the C++ compiler and related tools (usually with something like
apt-get install build-essential). However, for boost some care may be needed. TheSpecUtilsmodule is really a shared library that node.js loads.
From bash or the Windows Command Prompt, run:
# Globally install cmake-js
npm install -g cmake-js
# For macOS only, you may want to define a deployment target
export MACOSX_DEPLOYMENT_TARGET=10.15
cd /path/to/SpecUtils/bindings/node/
# Install dependency for compiling a node.js add-on
npm install --save-dev node-addon-api
# To build int the "build" directory, with default
cmake-js --CDSpecUtils_FLT_PARSE_METHOD="strtod"
# Note that the 'SpecUtils_FLT_PARSE_METHOD' options are "FastFloat", "FromChars",
# "boost", and "strtod"; See SpecUtils/CMakeLists.txt for details, but "strtod"
# should work everywhere. "FromChars" works with reasonably modern compilers, except
# not with the Xcode supplied compiler. "boost" is fastest, but requires boost
# installed. "FastFloat" is a great choice, and if its not in your include path,
# CMake will attempt to fetch it.
# Or to have a little more control over things
cmake-js --CDSpecUtils_FLT_PARSE_METHOD="strtod" --CDCMAKE_BUILD_TYPE="Release" --out="build_dir"
# If you make changes and want to recompile
cmake-js build --out="build_dir"
# Or you can use CMake to run the `make` command, which can be useful when
# the CMake generator isnt a command-line based system like (ex Xcode, MSVC)
cmake --build build_dir --config Release
# And also copy all the InterSpec resources to the
# 'app' sub-directory of your build dir
cmake-js build --out=build_dir --target install
# Or
cmake --build build_dir --target install --config Release
# These commands will copy 'SpecUtilsJS.node' and 'example.js'
# to 'node_release/' in your build directory, so you can run like:
node build_dir/node_release/example.js spectrum.n42
# Optionally convert to N42-2012 (will not overwrite an existing file)
node build_dir/node_release/example.js input.spc output.n42const specutils = require('./SpecUtilsJS.node');
// Open and parse spectrum file
let spec = new specutils.SpecFile( "spectrum.n42" );
// Print out a little info about detection system
console.log( "Manufacturer = " + spec.manufacturer() );
console.log( "Instrument Model = " + spec.instrumentModel() );
console.log( "Serial Number = '" + spec.serialNumber() + "'" );
// Print a little information about each record in the file
let records = spec.records();
for( let i = 0; i < records.length; ++i){
let record = records[i];
console.log( "SampleNumber " + record.sampleNumber() + ", DetectorName '"
+ record.detectorName() + "' has LiveTime " + record.liveTime() + " s"
+ ", ChannelCounts=" + record.gammaChannelContents() );
}
// Sum all spectrum that the file indicated was a foreground
// (aka item of interest), or didnt indicate the source type
// (almost always a foreground)
let summed = spec.sumRecords(null,null,["Foreground", "UnknownSourceType"]);
console.log( "Channel Counts = '" + summed.gammaChannelContents() + "'" );
// Write the file to a different format (will not overwrite existing files by default).
// You could also filter which records get saved with extra arguments.
spec.writeToFile( "output.pcf", "PCF" );Spectrum files can be quite varied in the information they contain, so SpecUtils tries to coalesce all formats to something almost kinda matching the N42-2012 representation model (not perfectly though).
Each spectrum file is represented as a SpecFile object that contains file-level information such as device manufacturer, serial number, RIID results, etc. This object also contains the gamma-spectra included in the file. Files may contain only a single spectrum, or they can contain spectrum from multiple different detection elements, and/or multiple measurements from each detection element. A spectrum and its related information is held by a SpecRecord object. To organize the spectra, each detection element is assigned a name (most file formats specify this name, if not a reasonable guess is made), and spectra spanning the same time periods are all assigned the same sample number. A detector name and sample number will uniquely specify a SpecRecord inside of a SpecFile.
A SpecRecord object contains zero or one spectrums, potentially neutron count information (if a neutron detector can be unambiguously grouped with a neutron detector, the information from both will be combined into one SpecRecord, otherwise they will be separate), as well as live time, real (clock) time, energy calibration information, and other related information. Each SpecRecord also has a SourceType assigned to it, which can be "Background", "Calibration", "Foreground", "IntrinsicActivity", or "UnknownSourceType"; this information can help you sort which SpecRecord you are interested in.
There are four main classes defined by this module: SpecFile, SpecRecord, RiidAnaResult, and RiidAnalysis. For all the classes, information is accessed through accessor functions. There are some additional classes defined (SourceType, OccupancyStatus, EquationType, DetectorType) solely to define constants.
-
SpecFileThis class represents a spectrum file on disk. Defined functions are:Constructor: Takes a single String argument that gives the filesystem path to spectrum file to parse. Throws exception if file can't be parsed.records( wantedDetectorNames, wantedSampleNumbers, wantedSourceTypes ): Returns an array ofSpecRecordobjects. If provided, the arguments filters theSpecRecords returned so that they match the provided filters.wantedDetectorNames: Either a single detector name, or an Array of detector names that should be included in returned results. Passingnullfor this argument results in using all detector names. If a detector name is not in theSpecFilethan an exception will be thrown.wantedSampleNumbers: Either a single Number, or an Array of Numbers specifying the sample numbers that should be included in the returned results. Passingnullfor this argument results in using all sample numbers. If any sample numbers are not in theSpecFile, an exception will be thrown.wantedSourceTypes: Either a single String or an Array of Strings specifying theSourceTypes to include in the result. Passingnullfor this argument results in using all source types. Valid strings are "Background", "Calibration", "Foreground", "IntrinsicActivity", and "UnknownSourceType", using an invalid string will result in an exception (these strings are also defined by theSourceTypestatic member variables likeSourceType.Background,SourceType.Foreground, etc). Note that "UnknownSourceType" is used when the spectrum file does not specify the source type, and it cant unambiguously be determined, however, these are almost always equivalent to "Foreground".
sampleNumbers( wantedSourceTypes ): Returns an Array of Numbers containing all the sample numbers in the spectrum file, passing the optionalSourceTypefiltering; array may be empty if noSpecRecords matched the optional filtering ofSourceTypes.wantedSourceTypes: Either null, a String, or an Array of Strings that specify the whichSourceTypes are wanted (eg, "Background", "Calibration", "Foreground", "IntrinsicActivity", and "UnknownSourceType"); has same semantics and meaning to the equivalent argument for therecords()function.
detectorNames(): Returns an Array of Strings containing the detector names in the spectrum file. Takes no arguments.sumRecords( wantedDetectorNames, wantedSampleNumbers, wantedSourceTypes ): Returns aSpecRecordobject, where all relevant gamma spectra, times, neutron counts, etc. have all been summed into a single record. The arguments have identical semantics and meaning to therecords()function.inferredInstrumentModel(): The instrument model as inferred by the parsing code. Will be "Unknown" if couldn't be determined, otherwise will be from a set number of strings, including "IdentiFINDER", "IdentiFINDER-NG", "Detective-EX100", etc. If this value is not "Unknown", then this is usually the most reliable way to determine detector type, as many spectrum file formats do not give model information as themanufacturer()andinstrumentModel()functions return.manufacturer(): Returns String giving manufacturers name, usually as specified in the file (maybe be empty String).instrumentModel(): Returns String giving instrument model, usually as specified in the file (maybe be empty String).serialNumber(): Returns String giving the serial number of the detector, as specified in the file (maybe be empty String).riidAnalysis(): Returns aRiidAnalysisobject if available in spectrum file, otherwisenull.writeToFile( path, format, wantedDetectorNames, wantedSampleNumbers, wantedSourceTypes, forceOverwrite ): Writes the spectrum file information to a new file. You must provide at least the first two arguments. Will throw exception on error.path: filesystem location, as a String, to write the file.format: Spectrum file format to write. Must be one of the following Strings: "TXT", "CSV", "PCF", "N42-2006", "N42-2012", "CHN", "SPC-int", "SPC", "SPC-float", "SPC-ascii", "GR130v0", "GR135v2", "SPE", "IAEA", "HTML".wantedDetectorNames,wantedSampleNumbers,wantedSourceTypes: same semantics and meaning as forrecords(). Allows filtering whichSpecRecordare included in the output file.forceOverwrite: A Boolean, that if specified will force overwriting the output file, if it already exists. If argument is not given, defaults tofalse.
isSearchMode(): Returns Boolean indicating if the spectrum file was a search-mode, or RPM measurement where there are samples for many consecutive time periods.hasGpsInfo(): Returns Boolean indicating if the file contained any GPS information.meanLatitude(): Returns Number indicating the mean latitude given for all the records in the file. Returnsnullif no GPS information is in the file.meanLongitude(): Returns Number indicating the mean longitude given for all the records in the file. Returnsnullif no GPS information is in the file.- Additional functions: gammaLiveTime, gammaRealTime, gammaCountSum, containedNeutrons, neutronCountSum, numGammaChannels, numSpecRecords, instrumentType, uuid, filename, remarks,
-
SpecRecordliveTime(): Returns Number giving live time, in seconds, of the records.realTime(): Returns Number giving real time (i.e., clock time), in seconds, of the records.detectorName(): Returns String giving detectors name.sampleNumber(): Returns Number giving sample number of record.sourceType(): Returns String giving source type. Will be one of "Background", "Calibration", "Foreground", "IntrinsicActivity", and "UnknownSourceType".startTime(): Returns the measurement start time as a Number giving the milliseconds after the Epoch. If no start time was given in the file, will return null.title(): Returns title of the record as a String. Maybe be an empty string.gammaChannelEnergies(): If the records includes a gamma spectrum, will return an Array of numbers representing the lower energy, in keV, of each gamma channel. If record does not have a gamma spectrum, will returnnull.gammaChannelContents(): If the records includes a gamma spectrum, will return an Array of numbers representing the channel counts for each gamma channel. If record does not have a gamma spectrum, will returnnull.- Functions not yet documented: remarks, occupied, gammaCountSum, containedNeutron, neutronCountsSum, hasGpsInfo, latitude, longitude, positionTime, energyCalibrationModel, energyCalibrationCoeffs, deviationPairs
-
RiidAnalysisClass representing the RIID analysis results included in the spectrum file.results: Returns Array ofRiidAnaResultobjects that give the nuclides or sources identified by the RIID algorithm. Returnsnullif RIID results not available in the file.- Functions not yet documented: remarks, algorithmName, algorithmCreator, algorithmDescription, algorithmResultDescription,
-
RiidAnaResultClass representing a RIID identification, usually a nuclide, or nuclear source.nuclide(): Returns String giving nuclide. May not strictly be a nuclide, but may be something like: "U-238", "U-ore", "HEU", "nuclear", "neutron", "Unknown", "Ind.", etc. Will returnnullif no identification is given (most commonly happens when thisRiidAnaResultis to give an activity or doe rate)nuclideType(): Returns String giving type of nuclide, usually something like "Industrial", "Medical", etc. Will returnnullwhen not provided in the spectrum file.idConfidence(): Returns String describing nuclide confidence. May be a number (ex. "9"), a word (ex "High"), a letter (ex 'L'), or a phrase. Will returnnullif not available.doseRate(): Returns dose rate Number in micro-sievert. Returnsnullif not available.detector(): Returns the name, as a String, of the detector this result corresponds to. If null or empty, then you should assume it is for all detectors in the file.remark(): Returns String giving remark given in the file. Returnsnullif one is not provided in spectrum file.
- Improvements in representing the C++ concepts and values in JavaScript
- Potentially allow mutating SpectrumFile objects, like with energy calibration, or adding or removing SpecRecord
- Expose the d3.js based representations.