Skip to content

Commit d3f3a14

Browse files
authored
Merge pull request #27 from snu-hanaro/feat/mandatory-loadcell-config
Feat/mandatory loadcell config
2 parents 2200fc9 + d0b8a27 commit d3f3a14

File tree

5 files changed

+127
-32
lines changed

5 files changed

+127
-32
lines changed

examples/config.xlsx

10 Bytes
Binary file not shown.

examples/global_config.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
# global_config.py
2+
# ------------------ Required ------------------
3+
# ------------ Load Cell Parameters ------------
4+
# gain = gain_offset + (gain_internal_resistance_kohm * 1000)[Ω] / gain_resistance[Ω]
5+
# bridge_output_mv [mV] = sensitivity_mv_per_v[mV/V] * excitation_voltage[V] * thrust[N] / (rated_capacity_kgf[kgf] * g[N/kgf])
6+
# measured_output_v = (bridge_output_mv / 1000)[V] * gain
7+
# thrust[N] = (bridge_output_mv / sensitivity_mv_per_v)[V] / excitation_voltage[V] * (rated_capacity_kgf * g)[N]
8+
# = (measured_output_v * 1000 / gain)[mV] / sensitivity_mv_per_v[mV/V] / excitation_voltage[V]
9+
# * (rated_capacity_kgf * g)[N]
10+
rated_capacity_kgf = 500 # 정격하중: rated capacity of load cell, kgf
11+
sensitivity_mv_per_v = 3 # 감도: sensitivity of load cell, mV/V
12+
gain_internal_resistance_kohm = (
13+
49.4 # amplifier-specific internal resistor of load cell, kΩ
14+
)
15+
gain_offset = 1 # gain offset of load cell, V
16+
17+
# ------------------ Optional ------------------
118
# ----------- Thrust Data Processing -----------
219
thrust_sep = "[,\t]" # separator for thrust data, character or Regex
320
thrust_header = None # header for thrust data (row number or None)
@@ -9,9 +26,6 @@
926
pressure_time_col_idx = 0 # index of datetime column
1027
pressure_col_idx = 2 # index of pressure column
1128
# ------------ Global Configuration ------------
12-
g = 9.80665 # gravitational acceleration, m/s^2
13-
rated_load = 500 # rated load of load cell, kgf
14-
rated_output = 3 # rated output of load cell, V
1529
frequency = 100 # Sampling rate, Hz
1630
cutoff_frequency = 30 # LPF, Hz
1731
gaussian_strong_sigma = 10 # sigma for strong gaussian filter

src/static_fire_toolkit/cli.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,35 @@ def _load_config(execution_root: Path, expt_name: str | None) -> dict[str, Any]:
5454
"totalmass [g]",
5555
"Nozzlediameter [mm]",
5656
"segment",
57-
"expt_input_voltage [V]",
57+
# Accept both legacy and new column names for excitation voltage
58+
# either "expt_excitation_voltage [V]" or legacy "expt_input_voltage [V]"
59+
"expt_excitation_voltage [V]",
5860
"expt_resistance [Ohm]",
5961
]
6062
missing = [col for col in required_columns if col not in config.columns]
63+
# Backward-compat: if the only missing item is the new excitation voltage column,
64+
# but the legacy column exists, treat as satisfied.
65+
if (
66+
"expt_excitation_voltage [V]" in missing
67+
and "expt_input_voltage [V]" in config.columns
68+
):
69+
missing = [col for col in missing if col != "expt_excitation_voltage [V]"]
6170
if missing:
6271
raise ValueError(f"Missing columns in configuration: {missing}")
6372

73+
# Backward-compatible read for excitation voltage
74+
if "expt_excitation_voltage [V]" in config.columns:
75+
expt_excitation_voltage = config["expt_excitation_voltage [V]"][idx]
76+
elif "expt_input_voltage [V]" in config.columns:
77+
expt_excitation_voltage = config["expt_input_voltage [V]"][idx]
78+
else:
79+
raise ValueError(
80+
"Missing excitation voltage column in configuration: expected 'expt_excitation_voltage [V]' or legacy 'expt_input_voltage [V]'"
81+
)
82+
6483
return {
6584
"expt_file_name": config["expt_file_name"][idx],
66-
"expt_input_voltage": config["expt_input_voltage [V]"][idx],
85+
"expt_excitation_voltage": expt_excitation_voltage,
6786
"expt_resistance": config["expt_resistance [Ohm]"][idx],
6887
"grain": {
6988
"OD": float(config["Outerdiameter [mm]"][idx]),
@@ -130,7 +149,7 @@ def cmd_thrust(args: argparse.Namespace) -> None:
130149
proc = ThrustPostProcess(
131150
data_raw=thrust_raw,
132151
file_name=cfg["expt_file_name"],
133-
input_voltage=cfg["expt_input_voltage"],
152+
excitation_voltage=cfg["expt_excitation_voltage"],
134153
resistance=cfg["expt_resistance"],
135154
execution_root=str(exec_root),
136155
)
@@ -209,7 +228,7 @@ def cmd_process(args: argparse.Namespace) -> None:
209228
thrust_proc = ThrustPostProcess(
210229
data_raw=thrust_raw,
211230
file_name=cfg["expt_file_name"],
212-
input_voltage=cfg["expt_input_voltage"],
231+
excitation_voltage=cfg["expt_excitation_voltage"],
213232
resistance=cfg["expt_resistance"],
214233
execution_root=str(exec_root),
215234
)

src/static_fire_toolkit/config_loader.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,21 @@
2424
class Config:
2525
"""Runtime configuration values used by processing modules."""
2626

27-
rated_output: float = 3.0 # rated output of load cell, V
28-
rated_load: float = 500.0 # rated load of load cell, kgf
29-
g: float = 9.80665 # gravitational acceleration, m/s^2
27+
# Load cell & acquisition (must be explicitly set by user)
28+
sensitivity_mv_per_v: float | None = None # load cell sensitivity (mV/V)
29+
rated_capacity_kgf: float | None = None # load cell rated capacity (kgf)
30+
gain_internal_resistance_kohm: float | None = (
31+
None # amplifier internal resistance (kOhm)
32+
)
33+
gain_offset: float | None = None # amplifier gain offset (volts)
34+
# Other parameters
3035
frequency: float = 100.0 # Sampling rate, Hz (Δt = 0.01 s)
31-
cutoff_frequency: float = 10.0 # Hz for LPF
36+
cutoff_frequency: float = 30.0 # Hz for LPF
3237
lowpass_order: int = 5 # order for lowpass filter
33-
gaussian_weak_sigma: float = 2.0 # sigma for weak gaussian filter
34-
gaussian_strong_sigma: float = 8.0 # sigma for strong gaussian filter
35-
start_criteria: float = 0.15 # Criteria for the starting point of a meaningful interval in thrust data processing
36-
end_criteria: float = 0.15 # Criteria for the ending point of a meaningful interval in thrust data processing
38+
gaussian_weak_sigma: float = 1.5 # sigma for weak gaussian filter
39+
gaussian_strong_sigma: float = 10.0 # sigma for strong gaussian filter
40+
start_criteria: float = 0.2 # Criteria for the starting point of a meaningful interval in thrust data processing
41+
end_criteria: float = 0.1 # Criteria for the ending point of a meaningful interval in thrust data processing
3742
thrust_sep: str = "," # separator for thrust data, character or Regex
3843
thrust_header: int | None = 0 # header for thrust data (row number or None)
3944
thrust_time_col_idx: int = 0 # index of time column
@@ -59,10 +64,31 @@ def _load_from_python(path: Path, base: Config) -> Config:
5964
if spec and spec.loader:
6065
mod = module_from_spec(spec)
6166
spec.loader.exec_module(mod) # type: ignore[reportAttributeAccessIssue]
67+
# Backward-compatible mapping from legacy names
68+
sensitivity = _read_attr(mod, "sensitivity_mv_per_v", base.sensitivity_mv_per_v)
69+
if sensitivity == base.sensitivity_mv_per_v:
70+
sensitivity = _read_attr(mod, "rated_output", sensitivity)
71+
72+
rated_capacity = _read_attr(mod, "rated_capacity_kgf", base.rated_capacity_kgf)
73+
if rated_capacity == base.rated_capacity_kgf:
74+
rated_capacity = _read_attr(mod, "rated_load", rated_capacity)
75+
76+
gain_internal_res_kohm = _read_attr(
77+
mod, "gain_internal_resistance_kohm", base.gain_internal_resistance_kohm
78+
)
79+
gain_offset = _read_attr(mod, "gain_offset", base.gain_offset)
80+
6281
return Config(
63-
rated_output=float(_read_attr(mod, "rated_output", base.rated_output)),
64-
rated_load=float(_read_attr(mod, "rated_load", base.rated_load)),
65-
g=float(_read_attr(mod, "g", base.g)),
82+
sensitivity_mv_per_v=(None if sensitivity is None else float(sensitivity)),
83+
rated_capacity_kgf=(
84+
None if rated_capacity is None else float(rated_capacity)
85+
),
86+
gain_internal_resistance_kohm=(
87+
None
88+
if gain_internal_res_kohm is None
89+
else float(gain_internal_res_kohm)
90+
),
91+
gain_offset=(None if gain_offset is None else float(gain_offset)),
6692
frequency=float(_read_attr(mod, "frequency", base.frequency)),
6793
cutoff_frequency=float(
6894
_read_attr(mod, "cutoff_frequency", base.cutoff_frequency)

src/static_fire_toolkit/post_process/thrust_post_processing.py

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def __init__(
5656
self,
5757
data_raw: pd.DataFrame,
5858
file_name: str,
59-
input_voltage: float,
59+
excitation_voltage: float,
6060
resistance: float,
6161
execution_root: str | None = None,
6262
) -> None:
@@ -65,7 +65,7 @@ def __init__(
6565
Args:
6666
data_raw (pd.DataFrame): Raw data containing time and thrust measurements.
6767
file_name (str): File name identifier.
68-
input_voltage (float): Input voltage used during measurement.
68+
excitation_voltage (float): Excitation voltage used during measurement.
6969
resistance (float): Resistance value used during measurement.
7070
execution_root (str | None): Base directory for logs/ and results/*.
7171
If None, defaults to current working directory.
@@ -79,9 +79,11 @@ def __init__(
7979

8080
# Load configuration parameters explicitly
8181
self._CFG = load_global_config(self._EXEC_ROOT)
82-
self._rated_output = self._CFG.rated_output
83-
self._rated_load = self._CFG.rated_load
82+
self._sensitivity_mv_per_v = self._CFG.sensitivity_mv_per_v
83+
self._rated_capacity_kgf = self._CFG.rated_capacity_kgf
8484
self._g = 9.80665 # gravitational acceleration, m/s^2
85+
self._gain_internal_resistance_kohm = self._CFG.gain_internal_resistance_kohm
86+
self._gain_offset = self._CFG.gain_offset
8587
self._frequency = self._CFG.frequency
8688
self._cutoff_frequency = self._CFG.cutoff_frequency
8789
self._lowpass_order = self._CFG.lowpass_order
@@ -155,7 +157,7 @@ def __init__(
155157
].copy()
156158
self._data_raw.columns = ["time", "thrust"]
157159
self._file_name: str = file_name
158-
self._input_voltage: float = input_voltage
160+
self._excitation_voltage: float = excitation_voltage
159161
self._resistance: float = resistance
160162

161163
self._logger.info("0. Initialization complete.")
@@ -200,14 +202,38 @@ def _convert_voltage_to_thrust(self, data: pd.DataFrame) -> pd.DataFrame:
200202
"""
201203
self._logger.info(" 1-1. Converting voltage to thrust.")
202204
try:
205+
# Validate required config parameters
206+
missing_params: list[str] = []
207+
if self._sensitivity_mv_per_v is None:
208+
missing_params.append("sensitivity_mv_per_v")
209+
if self._rated_capacity_kgf is None:
210+
missing_params.append("rated_capacity_kgf")
211+
if self._gain_internal_resistance_kohm is None:
212+
missing_params.append("gain_internal_resistance_kohm")
213+
if self._gain_offset is None:
214+
missing_params.append("gain_offset")
215+
if missing_params:
216+
self._logger.error(
217+
"Missing required load cell config: %s", ", ".join(missing_params)
218+
)
219+
raise ValueError(
220+
"Missing required load cell configuration: "
221+
+ ", ".join(missing_params)
222+
)
203223
# 전압 값을 추력으로 변환
224+
# Convert volts -> thrust(N) using sensitivity (mV/V), capacity(kgf), excitation voltage(V),
225+
# amplifier internal resistance (kOhm) and gain offset.
226+
# Formula adapted to new config naming; assumes linear behavior.
204227
data["thrust"] = (
205228
data["thrust"]
206229
* 1000
207-
/ (self._rated_output * self._input_voltage)
208-
* self._rated_load
230+
/ (self._sensitivity_mv_per_v * self._excitation_voltage)
231+
* self._rated_capacity_kgf
209232
* self._g
210-
/ (1 + 49.4 * 1000 / self._resistance)
233+
/ (
234+
self._gain_offset
235+
+ self._gain_internal_resistance_kohm * 1000 / self._resistance
236+
)
211237
)
212238
except Exception as e:
213239
self._logger.error("Error converting voltage to thrust: %s", e)
@@ -636,10 +662,12 @@ def _thrust_plot(self) -> None:
636662
)
637663

638664
ax.annotate(
639-
"Impulse " + str(round(self._impulse, 2)) + " Ns\n"
640-
"Input Voltage " + str(round(self._input_voltage, 2)) + " V\n"
641-
"Resistance " + str(round(self._resistance, 2)) + r" $\Omega$",
642-
xy=(0.72, 0.85),
665+
"Impulse " + str(round(self._impulse, 2)) + " Ns\n"
666+
"Excitation Voltage " + str(round(self._excitation_voltage, 2)) + " V\n"
667+
"Resistance "
668+
+ str(round(self._resistance, 2))
669+
+ r" $\Omega$",
670+
xy=(0.67, 0.85),
643671
xycoords="axes fraction",
644672
size=20,
645673
font="Arial",
@@ -707,7 +735,15 @@ def run(self) -> pd.DataFrame:
707735
config = pd.read_excel(config_path, sheet_name=0, header=0, index_col=0)
708736
idx = len(config) - 1
709737
expt_file_name = config["expt_file_name"][idx]
710-
expt_input_voltage = config["expt_input_voltage [V]"][idx]
738+
# Backward-compatible: read excitation voltage from new or legacy column
739+
if "expt_excitation_voltage [V]" in config.columns:
740+
expt_excitation_voltage = config["expt_excitation_voltage [V]"][idx]
741+
elif "expt_input_voltage [V]" in config.columns:
742+
expt_excitation_voltage = config["expt_input_voltage [V]"][idx]
743+
else:
744+
raise ValueError(
745+
"Missing excitation voltage column: expected 'expt_excitation_voltage [V]' or legacy 'expt_input_voltage [V]'"
746+
)
711747
expt_resistance = config["expt_resistance [Ohm]"][idx]
712748

713749
print(f"Loaded configuration for experiment: {expt_file_name}")
@@ -738,6 +774,6 @@ def run(self) -> pd.DataFrame:
738774
print("Successfully loaded input data file.")
739775

740776
process = ThrustPostProcess(
741-
thrust_data, expt_file_name, expt_input_voltage, expt_resistance
777+
thrust_data, expt_file_name, expt_excitation_voltage, expt_resistance
742778
)
743779
_ = process.run()

0 commit comments

Comments
 (0)