huge improvements, read below
* function get_entry_from_elabid moved to the top to be used by classes * class Layers renamed Layer * class Layer expanded and completed with all NeXus-needed metadata * classes improved with "quality" error management * class Material added, for now it fetches just compound_elabid and compund chemical formula (as attribute and method respectively) * class Material has get_compound() method which needs some commenting * DEBUG MODE improved to cover all cases: layer, sample, substr. and pld target; creates a new shortcut for prompting (read line 147) * removed herobrine (fuck i'm old aren't i)
This commit is contained in:
@@ -2,45 +2,6 @@ import os, json, requests
|
|||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from classes import Header
|
from classes import Header
|
||||||
|
|
||||||
class Layers:
|
|
||||||
'''
|
|
||||||
Layers(layer_data) - where layer_data is a Python dictionary.
|
|
||||||
|
|
||||||
eLabFTW experiments contain most of the data required by the NeXus file - although every layer is on a different eLab entry; unfortunately, some data like the target's chemical formula must be retrieved through additional HTTP requests. Attributes 'target_elabid', 'rheed_system_elabid' and 'laser_system_elabid' contain elabid's for these resources, which are all items.
|
|
||||||
'''
|
|
||||||
def __init__(self, layer_data):
|
|
||||||
try:
|
|
||||||
self.extra = layer_data["metadata_decoded"]["extra_fields"]
|
|
||||||
self.target_elabid = self.extra["Target"]["value"]
|
|
||||||
self.start_time = layer_data.get("created_at")
|
|
||||||
self.operator = layer_data.get("fullname")
|
|
||||||
self.description = layer_data.get("body")
|
|
||||||
self.deposition_time = self.extra["Duration"]["value"]
|
|
||||||
self.repetition_rate = self.extra["Repetition rate"]["value"]
|
|
||||||
self.number_of_pulses = float(self.deposition_time) * float(self.repetition_rate)
|
|
||||||
# TO-DO: remove trailing space on eLabFTW's template for deposition layers
|
|
||||||
self.temperature = self.extra["Heater temperature "]["value"]
|
|
||||||
self.heating_method = self.extra["Heating Method"]["value"]
|
|
||||||
except KeyError as k:
|
|
||||||
raise KeyError(f"The provided dictionary lacks a \"{k}\" key.")
|
|
||||||
|
|
||||||
class Entrypoint:
|
|
||||||
'''
|
|
||||||
Entrypoint(sample_data) - where sample_data is a Python dictionary.
|
|
||||||
|
|
||||||
The entrypoint is the starting point of the process of resolving the data chain. The entrypoint must be a dictionary containing the data of a sample, created directly from the JSON of the item endpoint on eLabFTW - which can be done through the function get_entry_from_elabid.
|
|
||||||
'''
|
|
||||||
def __init__(self, sample_data):
|
|
||||||
try:
|
|
||||||
self.extra = sample_data["metadata_decoded"]["extra_fields"]
|
|
||||||
self.linked_items = sample_data["items_links"]
|
|
||||||
self.batch_elabid = self.extra["Substrate batch"]["value"]
|
|
||||||
self.linked_experiments = sample_data["related_experiments_links"]
|
|
||||||
self.linked_experiments_elabid = [ i["entityid"] for i in self.linked_experiments ]
|
|
||||||
except KeyError as k:
|
|
||||||
raise KeyError(f"The provided dictionary lacks a \"{k}\" key.")
|
|
||||||
|
|
||||||
|
|
||||||
def get_entry_from_elabid(elabid, entryType="items"):
|
def get_entry_from_elabid(elabid, entryType="items"):
|
||||||
'''
|
'''
|
||||||
Function which returns entrypoint data (as dictionary) from its elabid.
|
Function which returns entrypoint data (as dictionary) from its elabid.
|
||||||
@@ -57,13 +18,140 @@ def get_entry_from_elabid(elabid, entryType="items"):
|
|||||||
else:
|
else:
|
||||||
raise ConnectionError(f"HTTP request failed with status code: {response.status_code}.")
|
raise ConnectionError(f"HTTP request failed with status code: {response.status_code}.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Layer:
|
||||||
|
'''
|
||||||
|
Layer(layer_data) - where layer_data is a Python dictionary.
|
||||||
|
|
||||||
|
eLabFTW experiments contain most of the data required by the NeXus file - although every layer is on a different eLab entry;
|
||||||
|
unfortunately, some data like the target's chemical formula must be retrieved through additional HTTP requests.
|
||||||
|
Attributes 'target_elabid', 'rheed_system_elabid' and 'laser_system_elabid' contain elabid's for these resources, which are all items.
|
||||||
|
'''
|
||||||
|
def __init__(self, layer_data):
|
||||||
|
try:
|
||||||
|
self.extra = layer_data["metadata_decoded"]["extra_fields"]
|
||||||
|
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.start_time = layer_data.get("created_at")
|
||||||
|
self.operator = layer_data.get("fullname")
|
||||||
|
self.description = layer_data.get("body")
|
||||||
|
self.deposition_time = self.extra["Duration"]["value"]
|
||||||
|
self.repetition_rate = self.extra["Repetition rate"]["value"]
|
||||||
|
try:
|
||||||
|
self.number_of_pulses = float(self.deposition_time) * float(self.repetition_rate)
|
||||||
|
except ValueError:
|
||||||
|
# Since number_of_pulses is required, if it can't be calculated raise error:
|
||||||
|
raise ValueError("""
|
||||||
|
Warning: either Duration or Repetition Rate are empty or invalid.
|
||||||
|
If you think this is an error, please edit your eLabFTW entry and retry.
|
||||||
|
Setting Number of Pulses to NoneType.
|
||||||
|
""")
|
||||||
|
# TO-DO: remove trailing space on eLabFTW's template for deposition layers
|
||||||
|
self.temperature = self.extra["Heater temperature "]["value"] # TYPO: trailing space, must fix on elabftw
|
||||||
|
self.process_pressure = self.extra["Process pressure "]["value"] # TYPO: trailing space, must fix on elabftw
|
||||||
|
# </todo>
|
||||||
|
self.heating_method = self.extra["Heating Method"]["value"]
|
||||||
|
self.layer_thickness = self.extra["Thickness"]["value"]
|
||||||
|
self.buffer_gas = self.extra["Buffer gas"]["value"]
|
||||||
|
self.heater_target_distance = self.extra["Heater-target distance"]["value"]
|
||||||
|
self.laser_fluence = self.extra["Laser Intensity"]["value"] # here fluence = intensity
|
||||||
|
self.laser_spot_area = self.extra["Spot Area"]["value"]
|
||||||
|
try:
|
||||||
|
self.laser_energy = float(self.laser_fluence) * float(self.laser_spot_area)
|
||||||
|
except ValueError:
|
||||||
|
# Since laser_energy is NOT required, if it can't be calculated warn user but allow the software to continue execution:
|
||||||
|
print("""
|
||||||
|
Warning: either Laser Intensity or Spot Area are empty or invalid.
|
||||||
|
If you think this is an error, please edit your eLabFTW entry and retry.
|
||||||
|
Setting Laser Energy to NoneType.
|
||||||
|
""")
|
||||||
|
# Placeholder
|
||||||
|
self.laser_energy = None
|
||||||
|
# Laser rasternig section
|
||||||
|
self.laser_rastering_geometry = self.extra["Laser Rastering Geometry"]["value"]
|
||||||
|
self.laser_rastering_positions = self.extra["Laser Rastering Position"]["value"]
|
||||||
|
self.laser_rastering_velocities = self.extra["Laser Rastering Speed"]["value"]
|
||||||
|
# Pre annealing section
|
||||||
|
self.pre_annealing_ambient_gas = self.extra["Buffer gas Pre"]["value"]
|
||||||
|
self.pre_annealing_pressure = self.extra["Process pressure Pre"]["value"]
|
||||||
|
self.pre_annealing_temperature = self.extra["Heater temperature Pre"]["value"]
|
||||||
|
self.pre_annealing_duration = self.extra["Duration Pre"]["value"]
|
||||||
|
# Post annealing section
|
||||||
|
self.post_annealing_ambient_gas = self.extra["Buffer gas PA"]["value"]
|
||||||
|
self.post_annealing_pressure = self.extra["Process pressure PA"]["value"]
|
||||||
|
self.post_annealing_temperature = self.extra["Heater temperature PA"]["value"]
|
||||||
|
self.post_annealing_duration = self.extra["Duration PA"]["value"]
|
||||||
|
# Rejected but suggested by the NeXus standard:
|
||||||
|
#self.laser_rastering_coefficients = None
|
||||||
|
except KeyError as k:
|
||||||
|
# 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:
|
||||||
|
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.")
|
||||||
|
|
||||||
|
class Entrypoint:
|
||||||
|
'''
|
||||||
|
Entrypoint(sample_data) - where sample_data is a Python dictionary.
|
||||||
|
|
||||||
|
The entrypoint is the starting point of the process of resolving the data chain.
|
||||||
|
The entrypoint must be a dictionary containing the data of a sample, created directly from the JSON of the item endpoint on eLabFTW - which can be done through the function get_entry_from_elabid.
|
||||||
|
'''
|
||||||
|
def __init__(self, sample_data):
|
||||||
|
try:
|
||||||
|
self.extra = sample_data["metadata_decoded"]["extra_fields"]
|
||||||
|
self.linked_items = sample_data["items_links"]
|
||||||
|
self.batch_elabid = self.extra["Substrate batch"]["value"]
|
||||||
|
self.linked_experiments = sample_data["related_experiments_links"]
|
||||||
|
self.linked_experiments_elabid = [ i["entityid"] for i in self.linked_experiments ]
|
||||||
|
except KeyError as k:
|
||||||
|
# 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:
|
||||||
|
raise KeyError(f"The provided dictionary lacks a \"{k}\" key. Check the sample entry on eLabFTW and make sure you used the correct Resource template.")
|
||||||
|
# Non-required attributes:
|
||||||
|
self.name = sample_data.get("title") or None # error prevention is more important than preventing empty fields here
|
||||||
|
|
||||||
|
class Material:
|
||||||
|
'''
|
||||||
|
Material(material_data) - where material_data is a Python dictionary.
|
||||||
|
|
||||||
|
Both a PLD Target and a Substrate are materials made of a certain compound, of which we want to know:
|
||||||
|
* Name and formula;
|
||||||
|
* Shape and dimensions;
|
||||||
|
* Misc.
|
||||||
|
'''
|
||||||
|
def __init__(self, material_data):
|
||||||
|
try:
|
||||||
|
self.extra = material_data["metadata_decoded"]["extra_fields"]
|
||||||
|
self.compound_elabid = self.extra["Compound"]["value"]
|
||||||
|
except KeyError as k:
|
||||||
|
# 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:
|
||||||
|
raise KeyError(f"The provided dictionary lacks a \"{k}\" key. Check the target/substrate entry on eLabFTW and make sure you used the correct Resource template.")
|
||||||
|
def get_compound(self):
|
||||||
|
compound_data = get_entry_from_elabid(self.compound_elabid, entryType="items")
|
||||||
|
formula = compound_data["metadata_decoded"]["extra_fields"].get("Chemical formula")
|
||||||
|
formula_value = formula.get("value")
|
||||||
|
return formula_value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__=="__main__":
|
||||||
print("===== DEBUG MODE! =====")
|
print(f"=======================\n===== DEBUG MODE! =====\n=======================\n")
|
||||||
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: ")
|
||||||
|
# TEST. In production the entryType will probably just be "items" since the entrypoint is an item (sample).
|
||||||
entryType = None
|
entryType = None
|
||||||
while entryType not in ["items", "experiments"]:
|
while entryType not in ["items", "experiments"]:
|
||||||
eT = input("Enter a valid entry type [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.")
|
||||||
|
pass
|
||||||
match eT:
|
match eT:
|
||||||
case "items" | "i" | "item":
|
case "items" | "i" | "item":
|
||||||
entryType = "items"
|
entryType = "items"
|
||||||
@@ -71,16 +159,24 @@ if __name__=="__main__":
|
|||||||
entryType = "experiments"
|
entryType = "experiments"
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
elabid = input("Input elabid here [default = 1108]: ") or 1108
|
# This will probably be reworked in production
|
||||||
|
try:
|
||||||
|
elabid = elabid
|
||||||
|
except NameError:
|
||||||
|
elabid = input("Input elabid here [default = 1108]: ") or 1108
|
||||||
data = get_entry_from_elabid(elabid, entryType)
|
data = get_entry_from_elabid(elabid, entryType)
|
||||||
if entryType == "experiments":
|
if entryType == "experiments":
|
||||||
layer = Layers(data)
|
layer = Layer(data)
|
||||||
result = layer.__dict__
|
result = layer.__dict__
|
||||||
result.pop("extra")
|
result.pop("extra")
|
||||||
print(result)
|
print(result)
|
||||||
elif entryType == "items":
|
elif entryType == "items":
|
||||||
sample = Entrypoint(data)
|
if data.get("category_title") == "Sample":
|
||||||
result = sample.__dict__
|
item = Entrypoint(data)
|
||||||
|
elif data.get("category_title") in ["PLD Target", "Substrate"]:
|
||||||
|
item = Material(data)
|
||||||
|
print(item.get_compound())
|
||||||
|
result = item.__dict__
|
||||||
result.pop("extra")
|
result.pop("extra")
|
||||||
print(result)
|
print(result)
|
||||||
# print(json.dumps(chain.sample_data))
|
# print(json.dumps(chain.sample_data))
|
||||||
|
|||||||
Reference in New Issue
Block a user