The package is based on 5.01 specification of the JCAMP-DX format. The following options from the standard are not yet implemented:
- Compressed number formats
- Peak annotation data
- XYDATA in its sequential (i.e. classic) format
These terms are used slightly diffrently from the original specification. The reason is that there is no clear difference between LDR (LINE-DATA-RECORD in the specification) containing meta-information and containing actual spectral/peak inforation. For that reason, in the package LDRs with meta-information are called 'data records' and LDRs with spectral/peak information are called 'data table record'
The original specification does not provide an option to print data-table-records in long CSV style, i.e. each line contains a pair X,Y separated by a separator. However, this format technically fits to XYDATA and XYPOINTS formats if the line break is put after each X,Y pair instead of filling in 80 characters. We find this format useful, so this was added as a feature.
pip install git+https://github.com/r-hyperspec/pyspc-write-jdx
Minial simple JDX:
from pyspc_write_jdx import SimpleJDX
jdx = SimpleJDX(
title="Some title",
data_type="INFRARED SPECTRUM",
xunits="l/CM",
origin="PySPC",
owner="Me",
xypoints=[[1, 2, 3], [4, 5, 6]],
single_column=True, # This is default
decimal_places=4, # This is default
)
# Get as a string
jdx.to_string()
# UserWarning: {
# "yunits": [
# "DATA-LABEL '##YUNITS=' is required."
# ]
# }
# warnings.warn(json.dumps(validations, indent=4))
# ##TITLE= Some title
# ##JCAMP-DX= 5.01
# ##DATA TYPE= INFRARED SPECTRUM
# ##XUNITS= l/CM
# ##YUNITS=
# ##FIRSTX= 1
# ##LASTX= 3
# ##XFACTOR= 1
# ##YFACTOR= 1
# ##NPOINTS= 3
# ##ORIGIN= PySPC
# ##OWNER= Me
# ##XYPOINTS= (XY..XY)
# 1.0000, 4.0000
# 2.0000, 5.0000
# 3.0000, 6.0000
# ##END=
# Save to a file
jdx.to_file("tmp.jdx")
Set some value after instance is created:
jdx.yunits.value = "ABSORBANCE"
jdx.yunits.comment = "You can even add a comment"
jdx.comments.value = "This is test comment"
jdx.to_string()
# ##TITLE= Some title
# ##JCAMP-DX= 5.01
# ##DATA TYPE= INFRARED SPECTRUM
# ##XUNITS= l/CM
# ##YUNITS= ABSORBANCE $$ You can even add a comment
# ##FIRSTX= 1
# ##LASTX= 3
# ##XFACTOR= 1
# ##YFACTOR= 1
# ##NPOINTS= 3
# ##ORIGIN= PySPC
# ##OWNER= Me
# ##= This is test comment
# ##XYPOINTS= (XY..XY)
# 1.0000, 4.0000
# 2.0000, 5.0000
# 3.0000, 6.0000
# ##END=
Custom class to avoid arguments that are always the same (i.e. xunits, data_type, etc.):
import datetime
from pyspc_write_jdx import SimpleJDX
from pyspc_write_jdx.data_records import StringDataRecord
class MyCustomIRSimpleJDX(SimpleJDX):
# Optinal: add your custom data record
# NOTE: User defined data recod labels must start with $
my_custom_record = StringDataRecord("$MY CUSTOM RECORD", choices=["A", "B"], required=True)
# Optinal: specifiy output data records and/or change the order
# NOTE: 'title' must be the first. All required data records must be presented
output_data_records = [
'title',
'jcamp_dx',
'data_type',
'long_date',
'my_custom_record',
'xunits',
'yunits',
'firstx',
'lastx',
'firsty',
'xfactor',
'yfactor',
'npoints',
'origin',
'owner',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.data_type.value = "INFRARED SPECTRUM"
self.xunits.value = "l/CM"
self.yunits.value = "ABSORBANCE"
self.origin.value = "PySPC"
self.origin.comment = "Generated by pyspc"
self.owner.value = "Me"
self.long_date.value = datetime.datetime.now()
# Try it out!s
jdx = MyCustomIRSimpleJDX(title="Some title", my_custom_record="A", xypoints=[[1, 2, 3], [4, 5, 6]])
# Get as a string
jdx.to_string()
# ##TITLE= Some title
# ##JCAMP-DX= 5.01
# ##DATA TYPE= INFRARED SPECTRUM
# ##LONG DATE= 2022/08/20 15:50:13.691015
# ##$MY CUSTOM RECORD= A
# ##XUNITS= l/CM
# ##YUNITS= ABSORBANCE
# ##FIRSTX= 1
# ##LASTX= 3
# ##XFACTOR= 1
# ##YFACTOR= 1
# ##NPOINTS= 3
# ##ORIGIN= PySPC $$ Generated by pyspc
# ##OWNER= Me
# ##XYPOINTS= (XY..XY)
# 1.0000, 4.0000
# 2.0000, 5.0000
# 3.0000, 6.0000
# ##END=
It is possible to generate multi-block JDX files:
from pyspc_write_jdx import CompoundJDX, SimpleJDX
# Create inner blocks with spectral data
jdx1 = SimpleJDX(title="Title1", xypoints=[[1, 2], [4, 5]])
jdx2 = SimpleJDX(title="Title2", xydata=[[10, 20], [40, 50]])
# Create compound JDX by providing inner simple JDXs
multi_block_jdx = CompoundJDX("This is big compound JDX", jdx1, jdx2)
# Save to a file
multi_block_jdx.to_file("test_multiblock.jdx")
# Print as a string
multi_block_jdx.to_string()
# ##TITLE= This is big compound JDX
# ##JCAMP-DX= 5.01
# ##DATA TYPE= LINK
# ##BLOCKS= 2
# ##TITLE= Title1
# ##JCAMP-DX= 5.01
# ##DATA TYPE=
# ##XUNITS=
# ##YUNITS=
# ##FIRSTX= 1
# ##LASTX= 2
# ##XFACTOR= 1
# ##YFACTOR= 1
# ##NPOINTS= 2
# ##ORIGIN=
# ##OWNER=
# ##XYPOINTS= (XY..XY)
# 1.0000, 4.0000
# 2.0000, 5.0000
# ##END=
# ##TITLE= Title2
# ##JCAMP-DX= 5.01
# ##DATA TYPE=
# ##XUNITS=
# ##YUNITS=
# ##FIRSTX= 10
# ##LASTX= 20
# ##XFACTOR= 1
# ##YFACTOR= 1
# ##NPOINTS= 2
# ##ORIGIN=
# ##OWNER=
# ##XYDATA= (X++(Y..Y))
# 10.0000 40.0000
# 20.0000 50.0000
# ##END=
# ##END=
It is also possible to add inner blocks dynamicaly:
# Create new data
jdx3 = SimpleJDX(title="Title3", xydata=[[100, 200], [400, 500]])
# Apped the new block
multi_block_jdx.add_block(jdx3)