from typing import Set
from SDF.data_model import ArrayData1D, ArrayDataset1D, Parameter, Workspace
# all other parameters are taken as string
REQUIRED_PARAMETERS: Set[str] = {
"spring_constant",
"sensitivity",
}
REQUIRED_SEGMENT_DATA_CHANNELS: Set[str] = {
"height",
"vDeflection",
"measuredHeight",
}
REQUIRED_SEGMENT_PARAMETERS: Set[str] = {
"duration",
"direction",
"velocity",
}
[docs]class ForceSDF(Workspace):
"""
Represents a force distance curve.
This class wraps a Workspace and ensures it fulfills all ForceSDF requirements.
"""
def __init__(self, workspace: Workspace):
if not isinstance(workspace, Workspace):
raise TypeError(f"Expected a Workspace, got {type(workspace)}")
# check name
if not workspace.name.startswith("ForceSDF"):
raise ValueError(f"Expected a ForceSDF workspace, got '{workspace.name}'")
super().__init__(name=workspace.name, owner=workspace.owner, date=workspace.date, comment=workspace.comment,
samples=workspace.samples, parameters=workspace.parameters, instruments=workspace.instruments,
datasets=workspace.datasets, workspaces=workspace.workspaces)
# check for required parameters
for par_name in REQUIRED_PARAMETERS:
if par_name not in self.instruments["parameters"].keys():
raise ValueError(f"Required parameter '{par_name}' is missing")
# check segments
for workspace in self.workspaces:
if not workspace.name.startswith("segment"): # skip non-segment workspaces
continue
self.workspaces.add(ForceSDFSegment(workspace, parent=self)) # replace workspace with segment
[docs]class ForceSDFSegment(Workspace):
parent: ForceSDF
def __init__(self, workspace: Workspace, parent: ForceSDF):
# check name
prefix = "segment "
name = workspace.name
if not (name.startswith(prefix) and name[len(prefix):].isdigit()):
raise ValueError(f"Expected a ForceSDF segment, got '{name}'")
super().__init__(name=workspace.name, owner=workspace.owner, date=workspace.date, comment=workspace.comment,
samples=workspace.samples, parameters=workspace.parameters, instruments=workspace.instruments,
datasets=workspace.datasets, workspaces=workspace.workspaces)
self.parent = parent
# generate missing data channels
self.__generate_missing_data_channels_and_parameters()
# check for required parameters
for par_name in REQUIRED_SEGMENT_PARAMETERS:
if par_name not in self.instruments["segment-parameters"].keys():
raise ValueError(f"Required parameter '{par_name}' is missing")
# check for required data channels
for channel_name in REQUIRED_SEGMENT_DATA_CHANNELS:
if channel_name not in self.datasets.keys():
raise ValueError(f"Required data channel '{channel_name}' is missing")
def __generate_missing_data_channels_and_parameters(self):
measured_height = self.datasets["measuredHeight"]
height = self.datasets["height"]
v_deflection = self.datasets["vDeflection"]
duration = self.instruments["segment-parameters"]["duration"]
spring_constant = self.parent.instruments["parameters"]["spring_constant"]
if not isinstance(measured_height, ArrayDataset1D):
raise TypeError("measuredHeight is no ArrayDataset")
if not isinstance(height, ArrayDataset1D):
raise TypeError("height is no ArrayDataset")
if not isinstance(v_deflection, ArrayDataset1D):
raise TypeError("vDeflection is no ArrayDataset")
# distance in meters
if v_deflection.unit == "N" and spring_constant.unit == "N/m":
distance = v_deflection.data / float(spring_constant.value)
distance_unit = "m"
elif v_deflection.unit == "m":
distance = v_deflection.data
distance_unit = "m"
else:
distance = v_deflection.data
distance_unit = None
# optional datasets
if measured_height.unit == "m" and distance_unit == "m":
measured_tip_sample_separation = ArrayData1D(measured_height.data + distance.data,
try_hex_transformation=True)
self.datasets.add(ArrayDataset1D("measuredTipSample", data=measured_tip_sample_separation, unit="m"))
if height.unit == "m" and distance_unit == "m":
tip_sample_separation = ArrayData1D(height.data + distance.data, try_hex_transformation=True)
self.datasets.add(ArrayDataset1D("tipSample", data=tip_sample_separation, unit="m"))
if measured_height.unit == "m" and duration.unit == "s":
velocity = (measured_height.data[0] - measured_height.data[-1]) / float(duration.value)
self.instruments["segment-parameters"].add(Parameter("velocity", velocity, "m/s"))