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.
Files changed (55) hide show
  1. {etlup-4.3.2 → etlup-4.3.3}/PKG-INFO +1 -1
  2. {etlup-4.3.2 → etlup-4.3.3}/pyproject.toml +1 -1
  3. etlup-4.3.3/src/etlup/module/ModuleIV.py +147 -0
  4. etlup-4.3.3/src/etlup/readout_board/ProbingTest.py +129 -0
  5. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/SensorIV.py +7 -3
  6. etlup-4.3.3/src/etlup/tamalero/__init__.py +0 -0
  7. etlup-4.3.2/src/etlup/module/ModuleIV.py +0 -87
  8. {etlup-4.3.2 → etlup-4.3.3}/README.md +0 -0
  9. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/__about__.py +0 -0
  10. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/__init__.py +0 -0
  11. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/base_model.py +0 -0
  12. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/custom_types.py +0 -0
  13. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/example_formatter.py +0 -0
  14. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/fake/__init__.py +0 -0
  15. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/fake/fake_test_component.py +0 -0
  16. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/fake/fake_test_module.py +0 -0
  17. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/gantry/PickAndPlace.py +0 -0
  18. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/gantry/SubassemblyAlignment.py +0 -0
  19. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/gantry/__init__.py +0 -0
  20. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/ETROCOpticalInspection.py +0 -0
  21. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridBaseline.py +0 -0
  22. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridIV.py +0 -0
  23. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridNoisewidth.py +0 -0
  24. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/HybridVisualInspection.py +0 -0
  25. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/ProbeCardI2CConnection.py +0 -0
  26. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/XRayInspection.py +0 -0
  27. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/hybrid/__init__.py +0 -0
  28. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/image_tests/ImageTest.py +0 -0
  29. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/image_tests/__init__.py +0 -0
  30. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/module/ModulePcbOpticalInspection.py +0 -0
  31. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/module/__init__.py +0 -0
  32. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/plot_utils.py +0 -0
  33. {etlup-4.3.2/src/etlup/sensor → etlup-4.3.3/src/etlup/readout_board}/__init__.py +0 -0
  34. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/ChargeCollection.py +0 -0
  35. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/CurrentStability.py +0 -0
  36. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/CurrentUniformity.py +0 -0
  37. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/GainCurve.py +0 -0
  38. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/GainLayerUniformity.py +0 -0
  39. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/InterpadResistance.py +0 -0
  40. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/InterpadWidth.py +0 -0
  41. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/MPVStability.py +0 -0
  42. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/OpticalInspection.py +0 -0
  43. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/SensorIrradiation.py +0 -0
  44. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/TestArrayCV.py +0 -0
  45. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/TestArrayIV.py +0 -0
  46. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sensor/TimeResolution.py +0 -0
  47. {etlup-4.3.2/src/etlup/tamalero → etlup-4.3.3/src/etlup/sensor}/__init__.py +0 -0
  48. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/sequences.py +0 -0
  49. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/Baseline.py +0 -0
  50. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/Mux64Values.py +0 -0
  51. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/Noisewidth.py +0 -0
  52. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/tamalero/ReadoutBoardCommunication.py +0 -0
  53. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/templates/sensors_plot.html +0 -0
  54. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/templates/test_by_image.html +0 -0
  55. {etlup-4.3.2 → etlup-4.3.3}/src/etlup/upload.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlup
3
- Version: 4.3.2
3
+ Version: 4.3.3
4
4
  Summary: Test definitions, plotting and documentation for the CMS MTD ETL project
5
5
  Author: Hayden Swanson
6
6
  Author-email: Hayden Swanson <hayden.swanson@cern.ch>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "etlup"
3
- version = "4.3.2"
3
+ version = "4.3.3"
4
4
  description = "Test definitions, plotting and documentation for the CMS MTD ETL project"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.8"
@@ -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 '&mdash;' 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