Compare commits
10 Commits
d86b35a5fe
...
async
| Author | SHA256 | Date | |
|---|---|---|---|
| 0a879cbfe9 | |||
| f60b58f2f2 | |||
| 6f618b2340 | |||
| 38940995b5 | |||
| f686ea65b1 | |||
| 23bfdefd30 | |||
| 38d281543e | |||
| a12506b8be | |||
| 43cfd788f3 | |||
| da42de5466 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1,9 @@
|
|||||||
# ignora log di h5tojson e jsontoh5
|
# ignores logs of h5tojson, jsontoh5
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# ignores output json of main.py
|
||||||
|
output/*.json
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
0
output/placeholder
Normal file
0
output/placeholder
Normal file
@@ -33,6 +33,8 @@ class APIHandler:
|
|||||||
raise ConnectionError(f"Invalid API key or authentication method.")
|
raise ConnectionError(f"Invalid API key or authentication method.")
|
||||||
case 404:
|
case 404:
|
||||||
raise ConnectionError(f"404: Not Found. This means there's no resource with this elabid (wrong elabid?) on your eLabFTW (wrong endpoint?).")
|
raise ConnectionError(f"404: Not Found. This means there's no resource with this elabid (wrong elabid?) on your eLabFTW (wrong endpoint?).")
|
||||||
|
case 400:
|
||||||
|
raise ConnectionError(f"400: Bad Request. This means the API endpoint you tried to reach is invalid. Did you tamper with the source code? If not, contact the developer.")
|
||||||
case _:
|
case _:
|
||||||
raise ConnectionError(f"HTTP request failed with status code: {response.status_code} (NOTE: 4xx means user's fault).")
|
raise ConnectionError(f"HTTP request failed with status code: {response.status_code} (NOTE: 4xx means user's fault).")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ class Layer:
|
|||||||
self.extra = layer_data["metadata_decoded"]["extra_fields"]
|
self.extra = layer_data["metadata_decoded"]["extra_fields"]
|
||||||
self.layer_number = self.extra["Layer Progressive Number"]["value"] # integer
|
self.layer_number = self.extra["Layer Progressive Number"]["value"] # integer
|
||||||
self.target_elabid = self.extra["Target"]["value"] # elabid
|
self.target_elabid = self.extra["Target"]["value"] # elabid
|
||||||
self.rheed_system_elabid = self.extra["RHEED System"]["value"] # elabid
|
|
||||||
self.laser_system_elabid = self.extra["Laser System"]["value"] # elabid
|
self.laser_system_elabid = self.extra["Laser System"]["value"] # elabid
|
||||||
|
self.chamber_elabid = self.extra["Chamber"]["value"] # elabid
|
||||||
|
self.rheed_system_elabid = self.extra["RHEED System"]["value"] # elabid
|
||||||
self.start_time = layer_data.get("created_at")
|
self.start_time = layer_data.get("created_at")
|
||||||
self.operator = layer_data.get("fullname")
|
self.operator = layer_data.get("fullname")
|
||||||
self.description = layer_data.get("body")
|
self.description = layer_data.get("body")
|
||||||
@@ -71,6 +72,16 @@ class Layer:
|
|||||||
# Some keys are not required and can be called through the .get() method - which is permissive and allows null values;
|
# Some keys are not required and can be called through the .get() method - which is permissive and allows null values;
|
||||||
# Other keys are required so if they can't be called (invalid or null) raise error and stop execution of the program:
|
# Other keys are required so if they can't be called (invalid or null) raise error and stop execution of the program:
|
||||||
raise KeyError(f"The provided dictionary lacks a \"{k}\" key. Check the deposition layer entry on eLabFTW and make sure you used the correct Experiment template.")
|
raise KeyError(f"The provided dictionary lacks a \"{k}\" key. Check the deposition layer entry on eLabFTW and make sure you used the correct Experiment template.")
|
||||||
|
def get_instruments(self, apikey):
|
||||||
|
raw_lasersys_data = APIHandler(apikey).get_entry_from_elabid(self.laser_system_elabid, entryType="items")
|
||||||
|
raw_chamber_data = APIHandler(apikey).get_entry_from_elabid(self.chamber_elabid, entryType="items")
|
||||||
|
raw_rheedsys_data = APIHandler(apikey).get_entry_from_elabid(self.rheed_system_elabid, entryType="items")
|
||||||
|
instruments_used = {
|
||||||
|
"laser_system": raw_lasersys_data.get("title") or None,
|
||||||
|
"deposition_chamber": raw_chamber_data.get("title") or None,
|
||||||
|
"rheed_system": raw_rheedsys_data.get("title") or None,
|
||||||
|
}
|
||||||
|
return instruments_used
|
||||||
|
|
||||||
class Entrypoint:
|
class Entrypoint:
|
||||||
'''
|
'''
|
||||||
@@ -157,6 +168,8 @@ class Target(Material):
|
|||||||
self.manufacturer = self.extra["Supplier"]["value"]
|
self.manufacturer = self.extra["Supplier"]["value"]
|
||||||
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:
|
||||||
|
self.description = material_data.get("body") or ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
165
src/main.py
165
src/main.py
@@ -98,71 +98,118 @@ def chain_layer_to_target(layer_object):
|
|||||||
material_object = call_material_from_elabid(target_elabid)
|
material_object = call_material_from_elabid(target_elabid)
|
||||||
return material_object
|
return material_object
|
||||||
|
|
||||||
#sample_object = call_entrypoint_from_elabid(elabid)
|
def deduplicate_instruments_from_layers(layers):
|
||||||
#from_entrypoint_to_material(sample_object)
|
'''
|
||||||
|
Takes a list of Layer-class objects and for each layer gets the instruments used (laser, depo chamber and RHEED), returns deduplicated list. Ideally, the lists should only contain one element.
|
||||||
|
'''
|
||||||
|
lasers = []
|
||||||
|
chambers = []
|
||||||
|
rheeds = []
|
||||||
|
for lyr in layers:
|
||||||
|
instruments = lyr.get_instruments(apikey)
|
||||||
|
lasers.append(instruments["laser_system"])
|
||||||
|
chambers.append(instruments["deposition_chamber"])
|
||||||
|
rheeds.append(instruments["rheed_system"])
|
||||||
|
instruments_used_dict = {
|
||||||
|
"laser_system": list( set( lasers ) ),
|
||||||
|
"deposition_chamber": list( set( chambers ) ),
|
||||||
|
"rheed_system" : list( set( rheeds ) ),
|
||||||
|
}
|
||||||
|
# lasers = { f"layer_{lyr.layer_number}": lyr.laser_system for lyr in layers }
|
||||||
|
# chambers = { f"layer_{lyr.layer_number}": lyr.deposition_chamber for lyr in layers }
|
||||||
|
# rheeds = { f"layer_{lyr.layer_number}": lyr.rheed_system for lyr in layers }
|
||||||
|
# instruments_used_dict = {
|
||||||
|
# "laser_system": lasers,
|
||||||
|
# "deposition_chamber": chambers,
|
||||||
|
# "rheed_system": rheeds,
|
||||||
|
# }
|
||||||
|
return instruments_used_dict
|
||||||
|
|
||||||
|
def make_nexus_schema_dictionary(substrate_object, layers):
|
||||||
|
'''
|
||||||
|
Main function, takes all the other functions to reconstruct the full dataset. Takes a Substrate-class object (output of the chain_entrypoint_to_batch() function) and a list of Layer-class objects (output of the chain_entrypoint_to_layers() function), returns dictionary with the same schema as the NeXus standard for PLD fabrications.
|
||||||
|
'''
|
||||||
|
pld_fabrication = {
|
||||||
|
"sample": {
|
||||||
|
"substrate": {
|
||||||
|
"name": substrate_object.name,
|
||||||
|
"chemical_formula" : substrate_object.get_compound_formula(apikey),
|
||||||
|
"orientation" : substrate_object.orientation,
|
||||||
|
"miscut_angle" : substrate_object.miscut_angle,
|
||||||
|
"miscut_direction" : substrate_object.miscut_direction,
|
||||||
|
"thickness" : substrate_object.thickness,
|
||||||
|
"dimensions" : substrate_object.dimensions,
|
||||||
|
"surface_treatment" : substrate_object.surface_treatment,
|
||||||
|
"manufacturer" : substrate_object.manufacturer,
|
||||||
|
"batch_id" : substrate_object.batch_id,
|
||||||
|
},
|
||||||
|
"multilayer": {},
|
||||||
|
},
|
||||||
|
"instruments_used": deduplicate_instruments_from_layers(layers),
|
||||||
|
}
|
||||||
|
multilayer = pld_fabrication["sample"]["multilayer"]
|
||||||
|
for layer in layers:
|
||||||
|
name = "layer_" + layer.layer_number
|
||||||
|
target_object = chain_layer_to_target(layer)
|
||||||
|
target_dict = {
|
||||||
|
"name": target_object.name,
|
||||||
|
"chemical_formula" : target_object.get_compound_formula(apikey),
|
||||||
|
"description" : target_object.description,
|
||||||
|
"shape" : target_object.shape,
|
||||||
|
"dimensions" : target_object.dimensions,
|
||||||
|
"thickness" : target_object.thickness,
|
||||||
|
"solid_form" : target_object.solid_form,
|
||||||
|
"manufacturer" : target_object.manufacturer,
|
||||||
|
# TO-DO: currently not available:
|
||||||
|
# "batch_id" : target_object.batch_id,
|
||||||
|
}
|
||||||
|
multilayer[name] = {
|
||||||
|
"target": target_dict,
|
||||||
|
"start_time": layer.start_time,
|
||||||
|
"operator": layer.operator,
|
||||||
|
"description": layer.description,
|
||||||
|
"number_of_pulses": layer.number_of_pulses,
|
||||||
|
"deposition_time": layer.deposition_time,
|
||||||
|
"temperature": layer.temperature,
|
||||||
|
"heating_method": layer.heating_method,
|
||||||
|
"layer_thickness": layer.layer_thickness,
|
||||||
|
"buffer_gas": layer.buffer_gas,
|
||||||
|
"process_pressure": layer.process_pressure,
|
||||||
|
"heater_target_distance": layer.heater_target_distance,
|
||||||
|
"repetition_rate": layer.repetition_rate,
|
||||||
|
"laser_fluence": layer.laser_fluence,
|
||||||
|
"laser_spot_area": layer.laser_spot_area,
|
||||||
|
"laser_energy": layer.laser_energy,
|
||||||
|
"laser_rastering": {
|
||||||
|
"geometry": layer.laser_rastering_geometry,
|
||||||
|
"positions": layer.laser_rastering_positions,
|
||||||
|
"velocities": layer.laser_rastering_velocities,
|
||||||
|
},
|
||||||
|
"pre_annealing": {
|
||||||
|
"ambient_gas": layer.pre_annealing_ambient_gas,
|
||||||
|
"pressure": layer.pre_annealing_pressure,
|
||||||
|
"temperature": layer.pre_annealing_temperature,
|
||||||
|
"duration": layer.pre_annealing_duration,
|
||||||
|
},
|
||||||
|
"post_annealing": {
|
||||||
|
"ambient_gas": layer.post_annealing_ambient_gas,
|
||||||
|
"pressure": layer.post_annealing_pressure,
|
||||||
|
"temperature": layer.post_annealing_temperature,
|
||||||
|
"duration": layer.post_annealing_duration,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return pld_fabrication
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__=="__main__":
|
||||||
print(f"=======================\n===== DEBUG MODE! =====\n=======================\n")
|
# TO-DO: place the API base URL somewhere else.
|
||||||
ELABFTW_API_URL = "https://elabftw.fisica.unina.it/api/v2"
|
ELABFTW_API_URL = "https://elabftw.fisica.unina.it/api/v2"
|
||||||
apikey = getpass("Paste API key here: ")
|
apikey = getpass("Paste API key here: ")
|
||||||
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)
|
||||||
batch = chain_entrypoint_to_batch(sample) # Material-class object
|
substrate_object = chain_entrypoint_to_batch(sample) # Substrate-class object
|
||||||
bd = batch.__dict__
|
|
||||||
bd.pop("extra")
|
|
||||||
layers = chain_entrypoint_to_layers(sample) # list of Layer-class objects
|
layers = chain_entrypoint_to_layers(sample) # list of Layer-class objects
|
||||||
print(f"Sample name:\n{sample.name}\n")
|
result = make_nexus_schema_dictionary(substrate_object, layers)
|
||||||
print(f"Substrate data:\n{bd}\n")
|
# print(make_nexus_schema_dictionary(substrate_object, layers)) # debug
|
||||||
print(f"Layers data:")
|
with open (f"output/sample-{elabid}.json", "w") as f:
|
||||||
for layer in layers:
|
json.dump(result, f, indent=3)
|
||||||
ld = layer.__dict__
|
|
||||||
ld.pop("extra")
|
|
||||||
tgt = chain_layer_to_target(layer)
|
|
||||||
td = tgt.__dict__
|
|
||||||
td.pop("extra")
|
|
||||||
print(ld)
|
|
||||||
print(td)
|
|
||||||
print()
|
|
||||||
|
|
||||||
# entryType = None
|
|
||||||
# while entryType not in ["items", "experiments"]:
|
|
||||||
# eT = input("Enter a valid entry type [items, experiments]: ")
|
|
||||||
# # This allows for a shortcut: instead of prompting the type before and the elabid after I can just prompt both at the same time - e.g. e51 is exp. 51, i1108 is item 1108...
|
|
||||||
# if eT[0] in ["e", "i"] and eT[-1].isnumeric():
|
|
||||||
# try:
|
|
||||||
# elabid = int(eT[1:])
|
|
||||||
# eT = eT[0]
|
|
||||||
# except Exception:
|
|
||||||
# print("Usage: i|item|items|i[ELABID] for items, e|experiment|experiments|e[ELABID] for experiments.")
|
|
||||||
# continue
|
|
||||||
# match eT:
|
|
||||||
# case "items" | "i" | "item":
|
|
||||||
# entryType = "items"
|
|
||||||
# case "experiments" | "e" | "exp" | "experiment":
|
|
||||||
# entryType = "experiments"
|
|
||||||
# case _:
|
|
||||||
# continue
|
|
||||||
# # This will probably be reworked in production
|
|
||||||
# try:
|
|
||||||
# elabid = elabid
|
|
||||||
# except NameError:
|
|
||||||
# elabid = input("Input elabid here [default = 1111]: ") or 1111
|
|
||||||
# data = APIHandler(apikey).get_entry_from_elabid(elabid, entryType)
|
|
||||||
# if entryType == "experiments":
|
|
||||||
# layer = Layer(data)
|
|
||||||
# result = layer.__dict__
|
|
||||||
# result.pop("extra")
|
|
||||||
# print(result)
|
|
||||||
# elif entryType == "items":
|
|
||||||
# if data.get("category_title") == "Sample":
|
|
||||||
# item = Entrypoint(data)
|
|
||||||
# elif data.get("category_title") in ["PLD Target", "Substrate"]:
|
|
||||||
# item = Material(data)
|
|
||||||
# print(item.get_compound_formula(apikey))
|
|
||||||
# else:
|
|
||||||
# raise Exception("The selected item or experiment is not in one of the following categories: [Sample, PLD Target, Substrate, PLD Deposition].")
|
|
||||||
# result = item.__dict__
|
|
||||||
# result.pop("extra")
|
|
||||||
# print(result)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user