Compare commits

...

5 Commits

Author SHA256 Message Date
207d166227 adds most of the required metadata to function build_nexus_file
the file is generated into the "output" folder w/ .h5 extension
the most has been done already (probably)
2026-02-16 15:43:07 +01:00
74b8c9cfae extends pld_fabrication dictionary with UoM's
now keys with numeric values are sub-dictionaries with a "value" and a
"units" key - unitS not unit to comply directly with NeXus format, which
turned out to be a good idea to avoid confusion since eLabFTW uses the
word "units" for the list of accepted units and "unit" for the selected
one...

NOTE: UoM = Unit of Measurement
2026-02-16 15:39:32 +01:00
1b1834d4e6 some attributes don't default to NoneType anymore
Target.description defaults to "" (empty str)
Substrate.thickness defaults to "" (empty str)
Substrate.thickness_unit is now hardcoded to "μm"
did you know? apparently h5py does NOT like null values
2026-02-16 15:35:22 +01:00
dfd3c07d2f ignores h5 and nxs files 2026-02-16 11:50:44 +01:00
d094a60725 replaces elabid with sample name in the names of output files 2026-02-16 11:49:48 +01:00
3 changed files with 164 additions and 26 deletions

4
.gitignore vendored
View File

@@ -1,8 +1,10 @@
# ignores logs of h5tojson, jsontoh5 # ignores logs of h5tojson, jsontoh5
*.log *.log
# ignores output json of main.py # ignores any output of main.py
output/*.json output/*.json
output/*.h5
output/*.nxs
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View File

@@ -175,7 +175,8 @@ class Substrate(Material):
self.miscut_angle_unit = self.extra["Miscut Angle"]["unit"] self.miscut_angle_unit = self.extra["Miscut Angle"]["unit"]
self.miscut_direction = self.extra["Miscut Direction"]["value"] self.miscut_direction = self.extra["Miscut Direction"]["value"]
# Not present (yet) on eLabFTW for Substrates: # Not present (yet) on eLabFTW for Substrates:
self.thickness = None #self.extra["Thickness"]["value"] self.thickness = "" #self.extra["Thickness"]["value"]
self.thickness_unit = "μm" #self.extra["Thickness"]["unit"]
self.surface_treatment = self.extra["Surface treatment"]["value"] self.surface_treatment = self.extra["Surface treatment"]["value"]
self.manufacturer = self.extra["Supplier"]["value"] self.manufacturer = self.extra["Supplier"]["value"]
self.batch_id = self.extra["Batch ID"]["value"] self.batch_id = self.extra["Batch ID"]["value"]
@@ -194,7 +195,7 @@ class Target(Material):
except KeyError as k: except KeyError as k:
raise KeyError(f"The provided dictionary lacks a \"{k}\" key - which is specific for PLD targets. Check the {self.name} target entry on eLabFTW and make sure you used the correct Resource template.") raise KeyError(f"The provided dictionary lacks a \"{k}\" key - which is specific for PLD targets. Check the {self.name} target entry on eLabFTW and make sure you used the correct Resource template.")
# Non-required attributes: # Non-required attributes:
self.description = material_data.get("body") or None self.description = material_data.get("body") or ""

View File

@@ -135,9 +135,15 @@ def make_nexus_schema_dictionary(substrate_object, layers):
"name": substrate_object.name, "name": substrate_object.name,
"chemical_formula" : substrate_object.get_compound_formula(apikey), "chemical_formula" : substrate_object.get_compound_formula(apikey),
"orientation" : substrate_object.orientation, "orientation" : substrate_object.orientation,
"miscut_angle" : substrate_object.miscut_angle, "miscut_angle" : {
"value": substrate_object.miscut_angle,
"units": substrate_object.miscut_angle_unit
},
"miscut_direction" : substrate_object.miscut_direction, "miscut_direction" : substrate_object.miscut_direction,
"thickness" : substrate_object.thickness, "thickness" : {
"value": substrate_object.thickness,
"units": substrate_object.thickness_unit,
},
"dimensions" : substrate_object.dimensions, "dimensions" : substrate_object.dimensions,
"surface_treatment" : substrate_object.surface_treatment, "surface_treatment" : substrate_object.surface_treatment,
"manufacturer" : substrate_object.manufacturer, "manufacturer" : substrate_object.manufacturer,
@@ -157,11 +163,14 @@ def make_nexus_schema_dictionary(substrate_object, layers):
"description" : target_object.description, "description" : target_object.description,
"shape" : target_object.shape, "shape" : target_object.shape,
"dimensions" : target_object.dimensions, "dimensions" : target_object.dimensions,
"thickness" : target_object.thickness, "thickness" : {
"value": target_object.thickness,
"units": target_object.thickness_unit,
},
"solid_form" : target_object.solid_form, "solid_form" : target_object.solid_form,
"manufacturer" : target_object.manufacturer, "manufacturer" : target_object.manufacturer,
"batch_id" : target_object.name,
# TO-DO: currently not available: # TO-DO: currently not available:
# "batch_id" : target_object.batch_id,
} }
multilayer[name] = { multilayer[name] = {
"target": target_dict, "target": target_dict,
@@ -169,17 +178,44 @@ def make_nexus_schema_dictionary(substrate_object, layers):
"operator": layer.operator, "operator": layer.operator,
"description": layer.description, "description": layer.description,
"number_of_pulses": layer.number_of_pulses, "number_of_pulses": layer.number_of_pulses,
"deposition_time": layer.deposition_time, "deposition_time": {
"temperature": layer.temperature, "value": layer.deposition_time,
"units": layer.deposition_time_unit,
},
"temperature": {
"value": layer.temperature,
"units": layer.temperature_unit,
},
"heating_method": layer.heating_method, "heating_method": layer.heating_method,
"layer_thickness": layer.layer_thickness, "layer_thickness": {
"value": layer.layer_thickness,
"units": layer.layer_thickness_unit,
},
"buffer_gas": layer.buffer_gas, "buffer_gas": layer.buffer_gas,
"process_pressure": layer.process_pressure, "process_pressure": {
"heater_target_distance": layer.heater_target_distance, "value": layer.process_pressure,
"repetition_rate": layer.repetition_rate, "units": layer.process_pressure_unit,
"laser_fluence": layer.laser_fluence, },
"laser_spot_area": layer.laser_spot_area, "heater_target_distance": {
"laser_energy": layer.laser_energy, "value": layer.heater_target_distance,
"units": layer.heater_target_distance_unit,
},
"repetition_rate": {
"value": layer.repetition_rate,
"units": layer.repetition_rate_unit,
},
"laser_fluence": {
"value": layer.laser_fluence,
"units": layer.laser_fluence_unit,
},
"laser_spot_area": {
"value": layer.laser_spot_area,
"units": layer.laser_spot_area_unit,
},
"laser_energy": {
"value": layer.laser_energy,
"units": layer.laser_energy_unit,
},
"laser_rastering": { "laser_rastering": {
"geometry": layer.laser_rastering_geometry, "geometry": layer.laser_rastering_geometry,
"positions": layer.laser_rastering_positions, "positions": layer.laser_rastering_positions,
@@ -187,15 +223,33 @@ def make_nexus_schema_dictionary(substrate_object, layers):
}, },
"pre_annealing": { "pre_annealing": {
"ambient_gas": layer.pre_annealing_ambient_gas, "ambient_gas": layer.pre_annealing_ambient_gas,
"pressure": layer.pre_annealing_pressure, "pressure": {
"temperature": layer.pre_annealing_temperature, "value": layer.pre_annealing_pressure,
"duration": layer.pre_annealing_duration, "units": layer.pre_annealing_pressure_unit,
},
"temperature": {
"value": layer.pre_annealing_temperature,
"units": layer.pre_annealing_temperature_unit,
},
"duration": {
"value": layer.pre_annealing_duration,
"units": layer.pre_annealing_duration_unit,
},
}, },
"post_annealing": { "post_annealing": {
"ambient_gas": layer.post_annealing_ambient_gas, "ambient_gas": layer.post_annealing_ambient_gas,
"pressure": layer.post_annealing_pressure, "pressure": {
"temperature": layer.post_annealing_temperature, "value": layer.post_annealing_pressure,
"duration": layer.post_annealing_duration, "units": layer.post_annealing_pressure_unit,
},
"temperature": {
"value": layer.post_annealing_temperature,
"units": layer.post_annealing_temperature_unit,
},
"duration": {
"value": layer.post_annealing_duration,
"units": layer.post_annealing_duration_unit,
},
}, },
} }
return pld_fabrication return pld_fabrication
@@ -209,11 +263,90 @@ def build_nexus_file(pld_fabrication, output_path):
# Sample section # Sample section
nx_sample = nx_pld_entry.create_group("sample") nx_sample = nx_pld_entry.create_group("sample")
nx_sample.attrs["NX_class"] = "NXsample" nx_sample.attrs["NX_class"] = "NXsample"
sample_dict = pld_fabrication["sample"]
# Substrate section # Substrate sub-section
nx_substrate = nx_pld_entry.create_group("substrate") nx_substrate = nx_sample.create_group("substrate")
nx_substrate.attrs["NX_class"] = "NXsubentry" nx_substrate.attrs["NX_class"] = "NXsubentry"
pass substrate_dict = sample_dict["substrate"]
try:
# Substrate fields (datasets)
nx_substrate.create_dataset("name", data=substrate_dict["name"])
nx_substrate.create_dataset("chemical_formula", data=substrate_dict["chemical_formula"])
nx_substrate.create_dataset("orientation", data=substrate_dict["orientation"])
nx_substrate.create_dataset("miscut_angle", data=substrate_dict["miscut_angle"]["value"]) # float
nx_substrate["miscut_angle"].attrs["units"] = substrate_dict["miscut_angle"]["units"]
nx_substrate.create_dataset("miscut_direction", data=substrate_dict["miscut_direction"])
nx_substrate.create_dataset("thickness", data=substrate_dict["thickness"]["value"]) # float/int
nx_substrate["thickness"].attrs["units"] = substrate_dict["thickness"]["units"]
nx_substrate.create_dataset("dimensions", data=substrate_dict["dimensions"])
nx_substrate.create_dataset("surface_treatment", data=substrate_dict["surface_treatment"])
nx_substrate.create_dataset("manufacturer", data=substrate_dict["manufacturer"])
nx_substrate.create_dataset("batch_id", data=substrate_dict["batch_id"])
except TypeError as te:
# sooner or later I'll handle this too - not today tho
raise TypeError(te)
# Multilayer sub-section
nx_multilayer = nx_sample.create_group("multilayer")
nx_multilayer.attrs["NX_class"] = "NXsubentry"
multilayer_dict = sample_dict["multilayer"]
# Repeat FOR EACH LAYER:
for layer in multilayer_dict:
nx_layer = nx_multilayer.create_group(layer)
nx_layer.attrs["NX_class"] = "NXsubentry"
layer_dict = multilayer_dict[layer]
# Sub-groups of a layer
nx_target = nx_layer.create_group("target")
nx_target.attrs["NX_class"] = "NXsample"
target_dict = layer_dict["target"]
nx_laser_rastering = nx_layer.create_group("laser_rastering")
nx_pre_annealing = nx_layer.create_group("pre_annealing")
nx_post_annealing = nx_layer.create_group("post_annealing")
nx_laser_rastering.attrs["NX_class"] = "NXprocess"
nx_pre_annealing.attrs["NX_class"] = "NXprocess"
nx_post_annealing.attrs["NX_class"] = "NXprocess"
try:
nx_target.create_dataset("name", data=target_dict["name"])
nx_target.create_dataset("chemical_formula", data=target_dict["chemical_formula"])
nx_target.create_dataset("description", data=target_dict["description"])
nx_target.create_dataset("shape", data=target_dict["shape"])
nx_target.create_dataset("dimensions", data=target_dict["dimensions"])
nx_target.create_dataset("thickness", data=target_dict["thickness"]["value"]) # float/int
nx_target["thickness"].attrs["units"] = target_dict["thickness"]["units"]
nx_target.create_dataset("solid_form", data=target_dict["solid_form"])
nx_target.create_dataset("manufacturer", data=target_dict["manufacturer"])
nx_target.create_dataset("batch_id", data=target_dict["batch_id"])
except TypeError as te:
raise TypeError(te)
try:
nx_layer.create_dataset("start_time", data = layer_dict["start_time"])
nx_layer.create_dataset("operator", data = layer_dict["operator"])
nx_layer.create_dataset("number_of_pulses", data = layer_dict["number_of_pulses"])
nx_layer.create_dataset("deposition_time", data = layer_dict["deposition_time"]["value"])
nx_layer["deposition_time"].attrs["units"] = layer_dict["deposition_time"]["units"]
nx_layer.create_dataset("repetition_rate", data = layer_dict["repetition_rate"]["value"])
nx_layer["repetition_rate"].attrs["units"] = layer_dict["repetition_rate"]["units"]
nx_layer.create_dataset("temperature", data = layer_dict["temperature"]["value"])
nx_layer["temperature"].attrs["units"] = layer_dict["temperature"]["units"]
nx_layer.create_dataset("heating_method", data = layer_dict["heating_method"])
nx_layer.create_dataset("layer_thickness", data = layer_dict["layer_thickness"]["value"])
nx_layer["layer_thickness"].attrs["units"] = layer_dict["layer_thickness"]["units"]
nx_layer.create_dataset("buffer_gas", data = layer_dict["buffer_gas"])
nx_layer.create_dataset("process_pressure", data = layer_dict["process_pressure"]["value"])
nx_layer["process_pressure"].attrs["units"] = layer_dict["process_pressure"]["units"]
nx_layer.create_dataset("heater_target_distance", data = layer_dict["heater_target_distance"]["value"])
nx_layer["heater_target_distance"].attrs["units"] = layer_dict["heater_target_distance"]["units"]
nx_layer.create_dataset("laser_fluence", data = layer_dict["laser_fluence"]["value"])
nx_layer["laser_fluence"].attrs["units"] = layer_dict["laser_fluence"]["units"]
nx_layer.create_dataset("laser_spot_area", data = layer_dict["laser_spot_area"]["value"])
nx_layer["laser_spot_area"].attrs["units"] = layer_dict["laser_spot_area"]["units"]
nx_layer.create_dataset("laser_energy", data = layer_dict["laser_energy"]["value"])
nx_layer["laser_energy"].attrs["units"] = layer_dict["laser_energy"]["units"]
except TypeError as te:
raise TypeError(te)
return
if __name__=="__main__": if __name__=="__main__":
# TO-DO: place the API base URL somewhere else. # TO-DO: place the API base URL somewhere else.
@@ -222,9 +355,11 @@ if __name__=="__main__":
elabid = input("Enter elabid of your starting sample [default= 1111]: ") or 1111 elabid = input("Enter elabid of your starting sample [default= 1111]: ") or 1111
data = APIHandler(apikey).get_entry_from_elabid(elabid) data = APIHandler(apikey).get_entry_from_elabid(elabid)
sample = Entrypoint(data) sample = Entrypoint(data)
sample_name = sample.name.strip().replace(" ","_")
substrate_object = chain_entrypoint_to_batch(sample) # Substrate-class object substrate_object = chain_entrypoint_to_batch(sample) # Substrate-class object
layers = chain_entrypoint_to_layers(sample) # list of Layer-class objects layers = chain_entrypoint_to_layers(sample) # list of Layer-class objects
result = make_nexus_schema_dictionary(substrate_object, layers) result = make_nexus_schema_dictionary(substrate_object, layers)
# print(make_nexus_schema_dictionary(substrate_object, layers)) # debug # print(make_nexus_schema_dictionary(substrate_object, layers)) # debug
with open (f"output/sample-{elabid}.json", "w") as f: with open (f"output/sample-{sample_name}.json", "w") as f:
json.dump(result, f, indent=3) json.dump(result, f, indent=3)
build_nexus_file(result, output_path=f"output/sample-{sample_name}-nexus.h5")