etlup 4.3.2__tar.gz → 4.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {etlup-4.3.2 → etlup-4.3.3}/PKG-INFO +1 -1
- {etlup-4.3.2 → etlup-4.3.3}/pyproject.toml +1 -1
- etlup-4.3.3/src/etlup/module/ModuleIV.py +147 -0
- etlup-4.3.3/src/etlup/readout_board/ProbingTest.py +129 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/SensorIV.py +7 -3
- etlup-4.3.3/src/etlup/tamalero/__init__.py +0 -0
- etlup-4.3.2/src/etlup/module/ModuleIV.py +0 -87
- {etlup-4.3.2 → etlup-4.3.3}/README.md +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/__about__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/base_model.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/custom_types.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/example_formatter.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/fake/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/fake/fake_test_component.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/fake/fake_test_module.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/gantry/PickAndPlace.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/gantry/SubassemblyAlignment.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/gantry/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/ETROCOpticalInspection.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridBaseline.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridIV.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridNoisewidth.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridVisualInspection.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/ProbeCardI2CConnection.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/XRayInspection.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/image_tests/ImageTest.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/image_tests/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/module/ModulePcbOpticalInspection.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/module/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/plot_utils.py +0 -0
- {etlup-4.3.2/src/etlup/sensor → etlup-4.3.3/src/etlup/readout_board}/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/ChargeCollection.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/CurrentStability.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/CurrentUniformity.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/GainCurve.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/GainLayerUniformity.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/InterpadResistance.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/InterpadWidth.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/MPVStability.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/OpticalInspection.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/SensorIrradiation.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/TestArrayCV.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/TestArrayIV.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/TimeResolution.py +0 -0
- {etlup-4.3.2/src/etlup/tamalero → etlup-4.3.3/src/etlup/sensor}/__init__.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sequences.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/Baseline.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/Mux64Values.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/Noisewidth.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/ReadoutBoardCommunication.py +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/templates/sensors_plot.html +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/templates/test_by_image.html +0 -0
- {etlup-4.3.2 → etlup-4.3.3}/src/etlup/upload.py +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from pydantic import ConfigDict, Field, model_validator
|
|
2
|
+
from typing_extensions import Union, Annotated, Literal, List, Optional
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
from .. import base_model as bm, jinja_env
|
|
6
|
+
from ..custom_types import Image
|
|
7
|
+
from ..plot_utils import convert_fig_to_html_img, image_src
|
|
8
|
+
|
|
9
|
+
# A 1x1 PNG, just enough for the example below to be a real image.
|
|
10
|
+
_EXAMPLE_IMAGE = (
|
|
11
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGO4AAAA0gDR29kOtwAAAABJRU5ErkJggg=="
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
class ModuleIVV0(bm.ConstructionBase):
|
|
15
|
+
model_config = ConfigDict(json_schema_extra={
|
|
16
|
+
'examples': [
|
|
17
|
+
{
|
|
18
|
+
"module": "MV4002",
|
|
19
|
+
"component_pos": 2,
|
|
20
|
+
"name": "module iv",
|
|
21
|
+
"measurement_date": "2023-01-01T12:00:00+01:00",
|
|
22
|
+
"location": "BU",
|
|
23
|
+
"user_created": "hayden",
|
|
24
|
+
"version": "v0",
|
|
25
|
+
"current": [0,1,2,3,4,5],
|
|
26
|
+
"voltage": [0,1,2,3,4,5],
|
|
27
|
+
"k_factor": [0,1,2,3,4,5],
|
|
28
|
+
"classification": "good",
|
|
29
|
+
"comment": "Breakdown well above the operating voltage.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"module": "MV4001",
|
|
33
|
+
"component_pos": 0,
|
|
34
|
+
"name": "module iv",
|
|
35
|
+
"measurement_date": "2023-01-01T12:00:00+01:00",
|
|
36
|
+
"location": "BU",
|
|
37
|
+
"user_created": "hayden",
|
|
38
|
+
"version": "v0",
|
|
39
|
+
"legacy_image_if_no_data": _EXAMPLE_IMAGE,
|
|
40
|
+
"comment": "Raw data lost, only the plot was kept.",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
'table': 'test',
|
|
44
|
+
'category': 'Module',
|
|
45
|
+
'module_types': [],
|
|
46
|
+
'component_types': [],
|
|
47
|
+
'description': (
|
|
48
|
+
'IV curve of a sensor on a module, classified as good, bad or weird (good when not given), '
|
|
49
|
+
'optionally with a comment. '
|
|
50
|
+
'Send current and voltage whenever you have them. legacy_image_if_no_data is only for '
|
|
51
|
+
'measurements whose raw data is gone and only a picture of the curve is left; it cannot '
|
|
52
|
+
'be sent together with current and voltage.'
|
|
53
|
+
)
|
|
54
|
+
})
|
|
55
|
+
name: Literal['module iv'] = "module iv"
|
|
56
|
+
version: Literal["v0"] = "v0"
|
|
57
|
+
|
|
58
|
+
module: str
|
|
59
|
+
component_pos: int = Field(ge=0, le=3, description="Component position (0-3)")
|
|
60
|
+
|
|
61
|
+
# Current and voltage are the measurement and should always be sent. They are only optional so
|
|
62
|
+
# that a measurement whose raw data was lost can still be recorded with legacy_image_if_no_data.
|
|
63
|
+
current: Optional[List[float]] = None
|
|
64
|
+
voltage: Optional[List[float]] = None
|
|
65
|
+
k_factor: Union[None, List[float]] = None
|
|
66
|
+
classification: Literal['good', 'bad', 'weird'] = 'good'
|
|
67
|
+
comment: Optional[str] = None
|
|
68
|
+
# Last resort, do not use if you have the data: a picture of the IV curve, for old measurements
|
|
69
|
+
# whose current and voltage no longer exist.
|
|
70
|
+
legacy_image_if_no_data: Optional[Image] = None
|
|
71
|
+
|
|
72
|
+
@model_validator(mode='after')
|
|
73
|
+
def data_or_legacy_image(self):
|
|
74
|
+
"""Either the measurement itself, or, only when it is gone, a picture of it."""
|
|
75
|
+
has_data = self.current is not None or self.voltage is not None
|
|
76
|
+
if self.legacy_image_if_no_data is not None:
|
|
77
|
+
if has_data or self.k_factor is not None:
|
|
78
|
+
raise ValueError('legacy_image_if_no_data is only for measurements whose data is gone. You sent current, voltage or k_factor, so send those and leave the image out.')
|
|
79
|
+
elif self.current is None or self.voltage is None:
|
|
80
|
+
raise ValueError('Both current and voltage are required.')
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
@model_validator(mode='after')
|
|
84
|
+
def same_lengths(self):
|
|
85
|
+
if self.current is not None and self.voltage is not None:
|
|
86
|
+
if len(self.current) != len(self.voltage):
|
|
87
|
+
raise ValueError(f'Current and voltage arrays should have the same lengths. Length of Current, Length of Voltage = ({len(self.current)}, {len(self.voltage)})')
|
|
88
|
+
if self.k_factor is not None and self.current is not None:
|
|
89
|
+
if len(self.k_factor) != len(self.current):
|
|
90
|
+
raise ValueError(f'k_factor and current arrays should have the same lengths. Length of k_factor, Length of Current = ({len(self.k_factor)}, {len(self.current)})')
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def plot(self):
|
|
94
|
+
if self.current is None: # only a legacy image, nothing to plot
|
|
95
|
+
return None
|
|
96
|
+
fig, ax = plt.subplots(figsize=(6, 4))
|
|
97
|
+
ax.plot(self.voltage, self.current, marker='o', label='IV Curve')
|
|
98
|
+
|
|
99
|
+
if self.k_factor is None:
|
|
100
|
+
plot_k_factor = [np.nan]
|
|
101
|
+
for i in range(1, len(self.current)):
|
|
102
|
+
delta_v = self.voltage[i] - self.voltage[i - 1]
|
|
103
|
+
current_i = self.current[i]
|
|
104
|
+
current_prev = self.current[i - 1]
|
|
105
|
+
|
|
106
|
+
if delta_v == 0 or current_i == 0:
|
|
107
|
+
plot_k_factor.append(np.nan)
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
k_val = ((current_i - current_prev) / delta_v) * (self.voltage[i] / current_i)
|
|
111
|
+
plot_k_factor.append(k_val)
|
|
112
|
+
else:
|
|
113
|
+
plot_k_factor = self.k_factor
|
|
114
|
+
|
|
115
|
+
ax2 = ax.twinx()
|
|
116
|
+
ax2.plot(self.voltage, plot_k_factor, marker='x', linestyle='--', color='orange', label='k_factor')
|
|
117
|
+
ax2.set_ylabel('k_factor')
|
|
118
|
+
|
|
119
|
+
ax.set_xlabel('Voltage (V)')
|
|
120
|
+
ax.set_ylabel('Current (A)')
|
|
121
|
+
ax.set_title(f'Module {self.module} - Component {self.component_pos}')
|
|
122
|
+
ax.grid(True)
|
|
123
|
+
handles1, labels1 = ax.get_legend_handles_labels()
|
|
124
|
+
handles2, labels2 = ax2.get_legend_handles_labels()
|
|
125
|
+
ax.legend(handles1 + handles2, labels1 + labels2, loc='best')
|
|
126
|
+
return fig
|
|
127
|
+
|
|
128
|
+
def html_display(self):
|
|
129
|
+
display_data = {
|
|
130
|
+
"classification": self.classification,
|
|
131
|
+
"comment": self.comment,
|
|
132
|
+
}
|
|
133
|
+
if self.legacy_image_if_no_data is not None:
|
|
134
|
+
plot = (
|
|
135
|
+
f'<p>Legacy image, the raw data for this measurement is no longer available.</p>'
|
|
136
|
+
f'<img src="{image_src(self.legacy_image_if_no_data)}" alt="{self.module} IV curve" '
|
|
137
|
+
'style="width: clamp(300px, 75vw, 1000px); height: auto;">'
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
plot = convert_fig_to_html_img(self.plot())
|
|
141
|
+
template = jinja_env.get_template('sensors_plot.html')
|
|
142
|
+
return template.render(
|
|
143
|
+
plot=plot,
|
|
144
|
+
display_data=display_data
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
ModuleIVType = Annotated[Union[ModuleIVV0], Field(discriminator="version")]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from pydantic import ConfigDict, Field
|
|
2
|
+
from typing_extensions import Annotated, Literal, Union, Optional
|
|
3
|
+
|
|
4
|
+
from .. import base_model as bm
|
|
5
|
+
|
|
6
|
+
# The rails probed on the board, as field name -> (the column it is under on the probing sheet, unit),
|
|
7
|
+
# in the order they are displayed.
|
|
8
|
+
_RAILS = {
|
|
9
|
+
'r2p': ('R2P', 'kOhm'),
|
|
10
|
+
'lv_rb': ('LV_RB', 'V'),
|
|
11
|
+
'rb_1v2': ('1V2_RB', 'V'),
|
|
12
|
+
'tx_2v5': ('2.5VTX', 'V'),
|
|
13
|
+
'rx_2v5': ('2.5VRX', 'V'),
|
|
14
|
+
}
|
|
15
|
+
# Each of the three slots has four reference voltages, held as slot_<slot>_vref_<vref>.
|
|
16
|
+
_SLOTS = range(3)
|
|
17
|
+
_VREFS = range(4)
|
|
18
|
+
|
|
19
|
+
def _show(value) -> str:
|
|
20
|
+
"""A probed value as it reads in the display, a dash when it was not probed."""
|
|
21
|
+
return '—' if value is None else str(value)
|
|
22
|
+
|
|
23
|
+
class ProbingTestV0(bm.ConstructionBase):
|
|
24
|
+
model_config = ConfigDict(json_schema_extra={
|
|
25
|
+
'examples': [
|
|
26
|
+
{
|
|
27
|
+
"component": "RB0001",
|
|
28
|
+
"name": "readout_board_probing",
|
|
29
|
+
"location": "BU",
|
|
30
|
+
"user_created": "hayden",
|
|
31
|
+
"version": "v0",
|
|
32
|
+
"measurement_date": "2026-11-10T12:01:00+01:00",
|
|
33
|
+
"r2p": 100.1,
|
|
34
|
+
"lv_rb": 9.1,
|
|
35
|
+
"rb_1v2": 1.199,
|
|
36
|
+
"tx_2v5": 2.509,
|
|
37
|
+
"rx_2v5": 2.617,
|
|
38
|
+
"slot_0_vref_0": 0.996,
|
|
39
|
+
"slot_0_vref_1": 0.996,
|
|
40
|
+
"slot_0_vref_2": 0.997,
|
|
41
|
+
"slot_0_vref_3": 0.998,
|
|
42
|
+
"slot_1_vref_0": 0.996,
|
|
43
|
+
"slot_1_vref_1": 0.995,
|
|
44
|
+
"slot_1_vref_2": 0.995,
|
|
45
|
+
"slot_1_vref_3": 0.996,
|
|
46
|
+
"slot_2_vref_0": 0.996,
|
|
47
|
+
"slot_2_vref_1": 0.996,
|
|
48
|
+
"slot_2_vref_2": 0.996,
|
|
49
|
+
"slot_2_vref_3": 0.995,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"component": "RB0002",
|
|
53
|
+
"name": "readout_board_probing",
|
|
54
|
+
"location": "BU",
|
|
55
|
+
"user_created": "hayden",
|
|
56
|
+
"version": "v0",
|
|
57
|
+
"measurement_date": "2026-11-10T12:05:00+01:00",
|
|
58
|
+
"r2p": 99.4,
|
|
59
|
+
"lv_rb": 9.1,
|
|
60
|
+
"rb_1v2": 1.198,
|
|
61
|
+
"tx_2v5": 2.558,
|
|
62
|
+
"rx_2v5": 2.573,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
'table': 'test',
|
|
66
|
+
'category': 'Readout Board',
|
|
67
|
+
'component_types': [],
|
|
68
|
+
'module_types': [],
|
|
69
|
+
'description': (
|
|
70
|
+
'Probing of the test points on a readout board: R2P in kOhm, the LV_RB, 1V2_RB, 2.5VTX '
|
|
71
|
+
'and 2.5VRX rails in V, and the four reference voltages (VREF0-3) of each of the three '
|
|
72
|
+
'slots in V. Every test point is optional, so only what was probed needs to be sent.'
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
name: Literal['readout_board_probing'] = 'readout_board_probing'
|
|
77
|
+
version: Literal["v0"] = "v0"
|
|
78
|
+
component: str
|
|
79
|
+
|
|
80
|
+
r2p: Optional[float] = Field(None, description='R2P, in kOhm')
|
|
81
|
+
lv_rb: Optional[float] = Field(None, description='LV_RB, in V')
|
|
82
|
+
rb_1v2: Optional[float] = Field(None, description='1V2_RB, in V')
|
|
83
|
+
tx_2v5: Optional[float] = Field(None, description='2.5VTX, in V')
|
|
84
|
+
rx_2v5: Optional[float] = Field(None, description='2.5VRX, in V')
|
|
85
|
+
|
|
86
|
+
# SLOT_<slot>_VREF<vref> on the probing sheet, in V.
|
|
87
|
+
slot_0_vref_0: Optional[float] = None
|
|
88
|
+
slot_0_vref_1: Optional[float] = None
|
|
89
|
+
slot_0_vref_2: Optional[float] = None
|
|
90
|
+
slot_0_vref_3: Optional[float] = None
|
|
91
|
+
slot_1_vref_0: Optional[float] = None
|
|
92
|
+
slot_1_vref_1: Optional[float] = None
|
|
93
|
+
slot_1_vref_2: Optional[float] = None
|
|
94
|
+
slot_1_vref_3: Optional[float] = None
|
|
95
|
+
slot_2_vref_0: Optional[float] = None
|
|
96
|
+
slot_2_vref_1: Optional[float] = None
|
|
97
|
+
slot_2_vref_2: Optional[float] = None
|
|
98
|
+
slot_2_vref_3: Optional[float] = None
|
|
99
|
+
|
|
100
|
+
def plot(self):
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def html_display(self) -> str:
|
|
104
|
+
rail_head = ''.join(f'<th>{label} ({unit})</th>' for label, unit in _RAILS.values())
|
|
105
|
+
rail_values = ''.join(f'<td>{_show(getattr(self, field))}</td>' for field in _RAILS)
|
|
106
|
+
vref_head = ''.join(f'<th>VREF{vref} (V)</th>' for vref in _VREFS)
|
|
107
|
+
vref_rows = ''.join(
|
|
108
|
+
f'<tr><th>Slot {slot}</th>'
|
|
109
|
+
+ ''.join(f'<td>{_show(getattr(self, f"slot_{slot}_vref_{vref}"))}</td>' for vref in _VREFS)
|
|
110
|
+
+ '</tr>'
|
|
111
|
+
for slot in _SLOTS
|
|
112
|
+
)
|
|
113
|
+
return f"""
|
|
114
|
+
<div class="table-responsive my-3">
|
|
115
|
+
<table class="table table-bordered table-sm w-auto mx-auto">
|
|
116
|
+
<thead><tr>{rail_head}</tr></thead>
|
|
117
|
+
<tbody><tr>{rail_values}</tr></tbody>
|
|
118
|
+
</table>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="table-responsive my-3">
|
|
121
|
+
<table class="table table-bordered table-sm w-auto mx-auto">
|
|
122
|
+
<thead><tr><th></th>{vref_head}</tr></thead>
|
|
123
|
+
<tbody>{vref_rows}</tbody>
|
|
124
|
+
</table>
|
|
125
|
+
</div>
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
ProbingTestType = Annotated[Union[ProbingTestV0], Field(discriminator="version")]
|
|
@@ -4,6 +4,10 @@ import matplotlib.pyplot as plt
|
|
|
4
4
|
from ..plot_utils import convert_fig_to_html_img
|
|
5
5
|
from .. import base_model, jinja_env
|
|
6
6
|
|
|
7
|
+
def _two_places(value):
|
|
8
|
+
"""A float as it is displayed, with 2 decimal places."""
|
|
9
|
+
return None if value is None else f'{value:.2f}'
|
|
10
|
+
|
|
7
11
|
class SensorIVV0(base_model.ConstructionBase):
|
|
8
12
|
model_config = ConfigDict(json_schema_extra={
|
|
9
13
|
'examples': [
|
|
@@ -54,7 +58,7 @@ class SensorIVV0(base_model.ConstructionBase):
|
|
|
54
58
|
if self.voltage is not None and self.current is not None:
|
|
55
59
|
ax.plot(self.voltage, self.current, label='IV Curve', marker='o')
|
|
56
60
|
if self.breakdown_voltage_V is not None:
|
|
57
|
-
ax.axvline(x=self.breakdown_voltage_V, color='green', linestyle='--', label=f'Breakdown Voltage: {self.breakdown_voltage_V} V')
|
|
61
|
+
ax.axvline(x=self.breakdown_voltage_V, color='green', linestyle='--', label=f'Breakdown Voltage: {_two_places(self.breakdown_voltage_V)} V')
|
|
58
62
|
ax.set_xlabel('Voltage (V)')
|
|
59
63
|
ax.set_ylabel('Current (A)')
|
|
60
64
|
if self.category is not None:
|
|
@@ -68,8 +72,8 @@ class SensorIVV0(base_model.ConstructionBase):
|
|
|
68
72
|
|
|
69
73
|
def html_display(self):
|
|
70
74
|
display_data = {
|
|
71
|
-
"leakage_current_uA": self.leakage_current_uA,
|
|
72
|
-
"breakdown_voltage_V": self.breakdown_voltage_V,
|
|
75
|
+
"leakage_current_uA": _two_places(self.leakage_current_uA),
|
|
76
|
+
"breakdown_voltage_V": _two_places(self.breakdown_voltage_V),
|
|
73
77
|
"category": self.category,
|
|
74
78
|
"gain_category": self.gain_category
|
|
75
79
|
}
|
|
File without changes
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
from pydantic import ConfigDict, Field, model_validator
|
|
2
|
-
from typing_extensions import Union, Annotated, Literal, List
|
|
3
|
-
import matplotlib.pyplot as plt
|
|
4
|
-
import numpy as np
|
|
5
|
-
from .. import base_model as bm
|
|
6
|
-
from ..plot_utils import convert_fig_to_html_img
|
|
7
|
-
|
|
8
|
-
class ModuleIVV0(bm.ConstructionBase):
|
|
9
|
-
model_config = ConfigDict(json_schema_extra={
|
|
10
|
-
'examples': [
|
|
11
|
-
{
|
|
12
|
-
"module": "MV4002",
|
|
13
|
-
"component_pos": 2,
|
|
14
|
-
"name": "module iv",
|
|
15
|
-
"measurement_date": "2023-01-01T12:00:00+01:00",
|
|
16
|
-
"location": "BU",
|
|
17
|
-
"user_created": "hayden",
|
|
18
|
-
"version": "v0",
|
|
19
|
-
"current": [0,1,2,3,4,5],
|
|
20
|
-
"voltage": [0,1,2,3,4,5],
|
|
21
|
-
"k_factor": [0,1,2,3,4,5]
|
|
22
|
-
},
|
|
23
|
-
],
|
|
24
|
-
'table': 'test',
|
|
25
|
-
'category': 'Module',
|
|
26
|
-
'module_types': [],
|
|
27
|
-
'component_types': [],
|
|
28
|
-
'description': 'IV curve of a sensor on a module.'
|
|
29
|
-
})
|
|
30
|
-
name: Literal['module iv'] = "module iv"
|
|
31
|
-
version: Literal["v0"] = "v0"
|
|
32
|
-
|
|
33
|
-
module: str
|
|
34
|
-
component_pos: int = Field(ge=0, le=3, description="Component position (0-3)")
|
|
35
|
-
|
|
36
|
-
current: List[float]
|
|
37
|
-
voltage: List[float]
|
|
38
|
-
k_factor: Union[None, List[float]] = None
|
|
39
|
-
|
|
40
|
-
@model_validator(mode='after')
|
|
41
|
-
def same_lengths(self):
|
|
42
|
-
if self.current is not None and self.voltage is not None:
|
|
43
|
-
if len(self.current) != len(self.voltage):
|
|
44
|
-
raise ValueError(f'Current and voltage arrays should have the same lengths. Length of Current, Length of Voltage = ({len(self.current)}, {len(self.voltage)})')
|
|
45
|
-
if self.k_factor is not None and self.current is not None:
|
|
46
|
-
if len(self.k_factor) != len(self.current):
|
|
47
|
-
raise ValueError(f'k_factor and current arrays should have the same lengths. Length of k_factor, Length of Current = ({len(self.k_factor)}, {len(self.current)})')
|
|
48
|
-
return self
|
|
49
|
-
|
|
50
|
-
def plot(self):
|
|
51
|
-
fig, ax = plt.subplots(figsize=(6, 4))
|
|
52
|
-
ax.plot(self.voltage, self.current, marker='o', label='IV Curve')
|
|
53
|
-
|
|
54
|
-
if self.k_factor is None:
|
|
55
|
-
plot_k_factor = [np.nan]
|
|
56
|
-
for i in range(1, len(self.current)):
|
|
57
|
-
delta_v = self.voltage[i] - self.voltage[i - 1]
|
|
58
|
-
current_i = self.current[i]
|
|
59
|
-
current_prev = self.current[i - 1]
|
|
60
|
-
|
|
61
|
-
if delta_v == 0 or current_i == 0:
|
|
62
|
-
plot_k_factor.append(np.nan)
|
|
63
|
-
continue
|
|
64
|
-
|
|
65
|
-
k_val = ((current_i - current_prev) / delta_v) * (self.voltage[i] / current_i)
|
|
66
|
-
plot_k_factor.append(k_val)
|
|
67
|
-
else:
|
|
68
|
-
plot_k_factor = self.k_factor
|
|
69
|
-
|
|
70
|
-
ax2 = ax.twinx()
|
|
71
|
-
ax2.plot(self.voltage, plot_k_factor, marker='x', linestyle='--', color='orange', label='k_factor')
|
|
72
|
-
ax2.set_ylabel('k_factor')
|
|
73
|
-
|
|
74
|
-
ax.set_xlabel('Voltage (V)')
|
|
75
|
-
ax.set_ylabel('Current (A)')
|
|
76
|
-
ax.set_title(f'Module {self.module} - Component {self.component_pos}')
|
|
77
|
-
ax.grid(True)
|
|
78
|
-
handles1, labels1 = ax.get_legend_handles_labels()
|
|
79
|
-
handles2, labels2 = ax2.get_legend_handles_labels()
|
|
80
|
-
ax.legend(handles1 + handles2, labels1 + labels2, loc='best')
|
|
81
|
-
return fig
|
|
82
|
-
|
|
83
|
-
def html_display(self):
|
|
84
|
-
fig = self.plot()
|
|
85
|
-
return convert_fig_to_html_img(fig)
|
|
86
|
-
|
|
87
|
-
ModuleIVType = Annotated[Union[ModuleIVV0], Field(discriminator="version")]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|