majoplot 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,433 @@
1
+ from __future__ import annotations
2
+ from typing import Protocol, runtime_checkable, NamedTuple, TextIO, Iterable, Optional, Callable, Iterator, Any
3
+ from collections.abc import MutableMapping
4
+ from dataclasses import dataclass, field
5
+ from numpy.typing import NDArray
6
+ import numpy as np
7
+ from numbers import Real
8
+ from typing import Generic, TypeVar
9
+
10
+ K = TypeVar("K")
11
+ V = TypeVar("V")
12
+
13
+ class FAIL:
14
+ __slots__ = ()
15
+ fail_signal = FAIL()
16
+ # ========= Data ========
17
+
18
+ class LabelValue(NamedTuple):
19
+ """
20
+ value: int | float | str
21
+ Note:
22
+ bool is not a supported value type.
23
+ Passing bool leads to undefined ordering behavior.
24
+ """
25
+ value: int|float|str|tuple[int|float|str]
26
+ unit: Optional[str] = None
27
+ unit_as_postfix: bool = True
28
+ def __lt__(self, other:LabelValue):
29
+ if not isinstance(other, LabelValue):
30
+ return NotImplemented
31
+
32
+ if isinstance(self.value, Real):
33
+ if isinstance(other.value, Real):
34
+ if self.unit == other.unit:
35
+ return self.value < other.value
36
+ elif self.unit is None:
37
+ return True
38
+ elif other.unit is None:
39
+ return False
40
+ else:
41
+ return self.unit.name < other.unit.name
42
+ else:
43
+ return True
44
+ elif isinstance(other.value, Real):
45
+ return False
46
+ else:
47
+ return str(self.value) < str(other.value)
48
+ def __str__(self):
49
+ if self.unit:
50
+ if self.unit_as_postfix:
51
+ return f"{self.value}{self.unit}"
52
+ else:
53
+ return f"{self.unit}{self.value}"
54
+ else:
55
+ return str(self.value)
56
+
57
+
58
+ class LabelDict(MutableMapping):
59
+ __slots__ = ("_items","summary_names","subgroup")
60
+ def __init__(self,initital:Optional[dict[str,LabelValue]]=None, summary_names:Optional[Iterable[str]]=None, subgroup:int=0):
61
+ self._items = dict(initital) if initital else {}
62
+ self.summary_names = list(summary_names) if summary_names else []
63
+ self.subgroup = subgroup
64
+ def __getitem__(self, key):
65
+ return self._items[key]
66
+ def __setitem__(self, key, value):
67
+ self._items[key] = value
68
+ def __delitem__(self, key):
69
+ del self._items[key]
70
+ def __iter__(self):
71
+ return iter(self._items)
72
+ def __len__(self):
73
+ return len(self._items)
74
+ def __str__(self):
75
+ return ", ".join(f"{name}:{value}" for (name, value) in self._items.items()).strip().strip(",")
76
+ def __copy__(self):
77
+ return LabelDict(
78
+ initital=dict(self._items),
79
+ summary_names=list(self.summary_names),
80
+ subgroup=self.subgroup
81
+ )
82
+
83
+ @property
84
+ def full_summary(self):
85
+ if self.summary_names:
86
+ return ", ".join(f"{name}:{self._items[name]}" for name in self.summary_names)
87
+ else:
88
+ return ", ".join(f"{name}:{value}" for (name, value) in self._items.items()).strip().strip(",")
89
+ @property
90
+ def brief_summary(self):
91
+ if self.summary_names:
92
+ return ",".join(f"{self._items[name]}" for name in self.summary_names if self._items[name]).strip().strip(",")
93
+ else:
94
+ return ",".join(f"{value}" for value in self._items.values() if value).strip().strip(",")
95
+
96
+ @classmethod
97
+ def group(cls,label_dict_element_pairs:Iterable[tuple[LabelDict,Any]],
98
+ /,
99
+ group_label_names:Iterable[str],
100
+ group_member_limit:int=0,
101
+ summary_label_names:Optional[Iterable[str]]=None,
102
+ sort_label_names:Optional[tuple[str]]=None,
103
+ )->tuple[list[tuple[LabelDict,list[Any]]], list[Any]]:
104
+ groups = {}
105
+ remains = []
106
+ group_label_names = list(group_label_names)
107
+ summary_label_names = list(summary_label_names) if summary_label_names else []
108
+ for label_dict, element in label_dict_element_pairs:
109
+ try:
110
+ group_label_values=tuple(label_dict[name] for name in group_label_names)
111
+ except KeyError:
112
+ remains.append(element)
113
+ continue
114
+ groups.setdefault(group_label_values,[]).append((label_dict, element))
115
+
116
+ if sort_label_names:
117
+ for label_dict_member_pairs in groups.values():
118
+ for sort_label_name in sort_label_names:
119
+ label_dict_member_pairs.sort(key=lambda x: x[0][sort_label_name])
120
+
121
+ if group_member_limit < 1:
122
+ return (list(
123
+ (
124
+ LabelDict(
125
+ initital = dict((name,value) for (name,value) in zip(group_label_names, group_label_values)),
126
+ summary_names=summary_label_names
127
+ ),
128
+ [label_dict_member_pair[1] for label_dict_member_pair in label_dict_member_pairs]
129
+ )
130
+ for (group_label_values, label_dict_member_pairs) in groups.items()
131
+ ), remains)
132
+ else:
133
+ subgroups = []
134
+ group_member_limit = int(group_member_limit)
135
+ for group_label_values, label_dict_member_pairs in groups.items():
136
+ group_dict = dict((name,value) for (name,value) in zip(group_label_names, group_label_values))
137
+ cur_subgroup = []
138
+ subgroup_end_i = group_member_limit-1
139
+ last_member_i = len(label_dict_member_pairs) - 1
140
+ for i,(_, member) in enumerate(label_dict_member_pairs):
141
+ subgroup_id = i // group_member_limit
142
+ subgroup_label_dict = LabelDict(initital=group_dict, summary_names=summary_label_names, subgroup=subgroup_id)
143
+ cur_subgroup.append(member)
144
+ if i == subgroup_end_i or i == last_member_i:
145
+ subgroups.append((subgroup_label_dict, cur_subgroup))
146
+ cur_subgroup = []
147
+ return subgroups, remains
148
+
149
+
150
+ @dataclass(slots=True)
151
+ class Data:
152
+ labels: LabelDict
153
+ _headers: tuple[str]
154
+ points:NDArray[np.floating]
155
+ ignore_outliers: Optional[IgnoreOutlierSpec] = None
156
+ unused: bool = False
157
+
158
+ headers: dict[str,int] = field(init=False, default=None)
159
+ _ignore_outliers_spec_cache:Optional[IgnoreOutlierSpec] = field(init=False, default=None)
160
+ _points_for_plot:Optional[NDArray[np.floating]] = field(init=False,default=None)
161
+ _x_for_plot:NDArray[np.floating] = field(init=False, default=None)
162
+ _y_for_plot:NDArray[np.floating] = field(init=False, default=None)
163
+ _xlim:tuple[np.floating,np.floating] = field(init=False, default=None)
164
+ _ylim:tuple[np.floating,np.floating] = field(init=False, default=None)
165
+
166
+ def __post_init__(self):
167
+ self.headers = dict((header, index) for (index, header) in enumerate(self._headers))
168
+
169
+ def __repr__(self):
170
+ return f"\nData({self.labels}\tShape: {self.points.shape})\n"
171
+
172
+ def _set_ignore_outliers(self):
173
+ min_gap_base = self.ignore_outliers.min_gap_base
174
+ min_gap_mutiple = self.ignore_outliers.min_gap_multiple
175
+ i = 1
176
+
177
+ def points_without_outliers():
178
+ cache_deque = []
179
+ for point in self.points:
180
+ cache_deque.append(point)
181
+ if len(cache_deque) == 3:
182
+ gap0to1 = abs(cache_deque[1][i] - cache_deque[0][i])
183
+ gap0to2 = abs(cache_deque[2][i] - cache_deque[0][i])
184
+ if gap0to2 < min_gap_base:
185
+ gap0to2 = min_gap_base
186
+ if gap0to1 > gap0to2 * min_gap_mutiple:
187
+ cache_deque.pop(1)
188
+ else:
189
+ yield cache_deque.pop(0)
190
+ yield from cache_deque
191
+
192
+ self._points_for_plot = np.array(list(points_without_outliers()))
193
+
194
+ @property
195
+ def points_for_plot(self)->NDArray[np.floating]:
196
+ if self.ignore_outliers is None:
197
+ return self.points
198
+ else:
199
+ if self._ignore_outliers_spec_cache != self.ignore_outliers:
200
+ self._set_ignore_outliers()
201
+ self._ignore_outliers_spec_cache = self.ignore_outliers
202
+ return self._points_for_plot
203
+
204
+
205
+ @property
206
+ def x_for_plot(self)->NDArray[np.floating]:
207
+ if self._x_for_plot is None:
208
+ self._x_for_plot = self.points_for_plot[:,0]
209
+ return self._x_for_plot
210
+
211
+ @property
212
+ def xlim(self)->tuple[np.float64,np.float64]:
213
+ if self._xlim is None:
214
+ self._xlim = (np.min(self.x_for_plot), np.max(self.x_for_plot))
215
+ return self._xlim
216
+
217
+ @property
218
+ def y_for_plot(self)->NDArray[np.floating]:
219
+ if self._ylim is None:
220
+ self._y_for_plot = self.points_for_plot[:,1]
221
+ return self._y_for_plot
222
+
223
+ @property
224
+ def ylim(self)->tuple[np.float64,np.float64]:
225
+ if self._ylim is None:
226
+ self._ylim = (min(self.y_for_plot), max(self.y_for_plot))
227
+ return self._ylim
228
+
229
+ @property
230
+ def label_for_plot(self)->str:
231
+ if self.labels:
232
+ return self.labels.brief_summary
233
+ else:
234
+ return ""
235
+
236
+
237
+
238
+ # ======== Plot Object ========
239
+ @dataclass(slots=True)
240
+ class Axes:
241
+ spec: AxesSpec
242
+ labels: LabelDict
243
+ data_pool: list[Data] = field(default_factory=list)
244
+
245
+ _xlim:tuple[np.float64,np.float64] = field(init=False, default=None)
246
+ _ylim:tuple[np.float64,np.float64] = field(init=False, default=None)
247
+
248
+ @property
249
+ def xlim(self)->tuple[np.float64,np.float64]:
250
+ if self._xlim is None:
251
+ xlims = (np.array([data.xlim for data in self.data_pool]))
252
+ self._xlim = np.min(xlims[:,0]), np.max(xlims[:,1])
253
+ return self._xlim
254
+
255
+ @property
256
+ def ylim(self)->tuple[np.float64,np.float64]:
257
+ if self._ylim is None:
258
+ ylims = np.array([data.ylim for data in self.data_pool])
259
+ self._ylim = (np.min(ylims[:,0]), np.max(ylims[:,1]))
260
+ return self._ylim
261
+
262
+
263
+ @dataclass(slots=True)
264
+ class Figure:
265
+ axes_pool: list[Axes]
266
+ spec: FigureSpec
267
+ muti_axes_spec: MutiAxesSpec
268
+ labels: LabelDict
269
+ proj_folder: dict[str,str] # Mapping from proj_name to folder_path_in_proj
270
+
271
+ # ======== Plot Spec ========
272
+ class AxesSpec(NamedTuple):
273
+ # Alaways has full Frame
274
+ x_axis_title: str
275
+ y_axis_title: str
276
+ axis_title_font_size:float = 8
277
+ x_left_lim: Optional[float] = None
278
+ x_right_lim: Optional[float] = None
279
+ y_left_lim: Optional[float] = None
280
+ y_right_lim: Optional[float] = None
281
+ x_log_scale_min_span:float = 1e12
282
+ y_log_scale_min_span:float = 1e12
283
+ linewidth:float = 1.0
284
+ marker_size:float = 4
285
+ major_tick: Optional[TickSpec] = None
286
+ minor_tick: Optional[TickSpec] = None
287
+ major_grid: Optional[GridSpec] = None
288
+ minor_grid: Optional[GridSpec] = None
289
+ legend: Optional[LegendSpec] = None
290
+ annotation: Optional[list[AnnotationSpec]] = None
291
+
292
+ class TickSpec(NamedTuple):
293
+ axis:str = "both"
294
+ direction:str = "in"
295
+ length:float = 6
296
+ width:float = 1.2
297
+ top:bool = True
298
+ left:bool = True
299
+
300
+
301
+ class GridSpec(NamedTuple):
302
+ linestyle:str = "--"
303
+ linewidth:float = 0.5
304
+ color:str = "grey"
305
+
306
+ class LegendSpec(NamedTuple):
307
+ loc:str = "upper right"
308
+ anchor_x:float = 1
309
+ anchor_y:float = 1
310
+ fontsize:float = 8
311
+ frameon:bool = True
312
+
313
+ class AnnotationSpec(NamedTuple):
314
+ text:str
315
+ text_x:float
316
+ text_y:float
317
+ fontsize:float = 8
318
+ arrow:Optional[ArrowSpec] = None
319
+
320
+ class ArrowSpec(NamedTuple):
321
+ point_x:float
322
+ point_y:float
323
+ arrowstyle:str = "->",
324
+ color:str = "black",
325
+ linewidth:float = 1.0
326
+
327
+ class IgnoreOutlierSpec(NamedTuple):
328
+ min_gap_base:float
329
+ min_gap_multiple:float = 20
330
+
331
+
332
+ class FigureSpec(NamedTuple):
333
+ name:str
334
+ title:str = None
335
+ figsize:tuple[float] = (3.4,2.6)
336
+ dpi:int = 300
337
+ global_fontsize:float = 8
338
+ facecolor:str = "white"
339
+ linestyle_cycle:tuple[str] = ("-", "--")
340
+ linecolor_cycle:tuple[str] = ("black", "red")
341
+ linemarker_cycle:tuple[str] = ("o","o","s","s","^","^","v","v","d","d","*","*","x","x","+","+")
342
+ alpa_cycle:tuple[float] = (1.0,)
343
+ legend:Optional[LegendSpec] = None
344
+
345
+
346
+ class MutiAxesSpec(Protocol):
347
+ legend_holder:str
348
+ @classmethod
349
+ def default(cls, figuresize:tuple[float], axes_pool:list[Axes]):
350
+ ...
351
+
352
+ # ======== Proj & folder ========
353
+ class folder(dict[str:Figure]):
354
+ pass
355
+
356
+ class Project(dict[str:folder]):
357
+ pass
358
+
359
+ # ======== Scenario ========
360
+ class Importer(Protocol):
361
+ instrument: str
362
+
363
+ @classmethod
364
+ def fetch_raw_data(cls, raw_data_file:TextIO, raw_data_name:str)->Data|FAIL:
365
+ ...
366
+
367
+
368
+ class Scenario(Protocol):
369
+ data_summary_label_names:list[str]
370
+ axes_label_names:tuple[str]
371
+ figure_label_names:tuple[str]
372
+ figure_summary_label_names:list[str]
373
+ max_axes_in_one_figure:int
374
+ project_to_child_folder_label_names: str
375
+ parent_folder_name: str
376
+
377
+ @classmethod
378
+ def preprocess(cls, raw_datas:list[Data])->list[Data]:
379
+ ...
380
+
381
+ @classmethod
382
+ def make_axes_spec(cls, axes_labels:LabelDict)->AxesSpec:
383
+ ...
384
+
385
+ @classmethod
386
+ def make_figure_spec(cls,figure_labels:LabelDict, axes_pool:Iterable[Axes])->FigureSpec:
387
+ ...
388
+
389
+ @classmethod
390
+ def make_muti_axes_spec(cls, axes_pool:list[Axes])->MutiAxesSpec|FAIL:
391
+ ...
392
+
393
+ #======== Fit ========
394
+ # @dataclass(slots=True)
395
+ # class FitData:
396
+ # labels: LabelDict
397
+ # x: NDArray
398
+ # y: NDArray
399
+
400
+ # class FitScenario(Scenario, Protocol):
401
+ # @classmethod
402
+ # def fit(cls, data:FitData, modelstrategy:ModelStrategy, optimizer:Callable)->NDArray:
403
+ # ...
404
+
405
+ # class ModelStrategy(Protocol):
406
+ # fit_figure_spec: FigureSpec
407
+ # @staticmethod
408
+ # def prepare_data_for_fit(all_datas:Iterable[Data])->list[FitData]:
409
+ # ...
410
+ # @staticmethod
411
+ # def model(self, x)->Callable:
412
+ # ...
413
+ # def residual(self, x):
414
+ # ...
415
+
416
+ # @runtime_checkable
417
+ # class HasJacobian(Protocol):
418
+ # def jacobian(self, x):
419
+ # ...
420
+
421
+ # @runtime_checkable
422
+ # class HasInitialGuess(Protocol):
423
+ # def initial_guess(self):
424
+ # ...
425
+
426
+ # @runtime_checkable
427
+ # class HasTransfrom(Protocol):
428
+ # @staticmethod
429
+ # def from_physics(x):
430
+ # ...
431
+ # @staticmethod
432
+ # def to_physics(x):
433
+ # ...
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+ import numpy as np
3
+
4
+ from ..base import *
5
+ from ..utils import skip_lines_then_readline
6
+
7
+ ROW_FILEOPENTIME = 3
8
+ ROW_SAMPLE1_NAME = 6
9
+ ROW_SAMPLE1_UNITS = 9
10
+ ROW_SAMPLE2_NAME = 10
11
+ ROW_SAMPLE2_UNITS = 13
12
+ ROW_SAMPLE3_NAME = 14
13
+ ROW_SAMPLE3_UNITS = 17
14
+ ROW_HEADERS = 32
15
+
16
+ T = "Temperature (K)"
17
+ H = "Magnetic Field (Oe)"
18
+ R1, I1 = "Bridge 1 Resistivity (Ohm-m)", "Bridge 1 Excitation (uA)"
19
+ R2, I2 = "Bridge 2 Resistivity (Ohm-m)", "Bridge 2 Excitation (uA)"
20
+ R3, I3 = "Bridge 3 Resistivity (Ohm-m)", "Bridge 3 Excitation (uA)"
21
+
22
+ class PPMS_Resistivity:
23
+ instrument = "PPMS"
24
+
25
+ @classmethod
26
+ def fetch_raw_data(cls, raw_data_file:TextIO, raw_data_name:str)->Data|FAIL:
27
+ labels = LabelDict(
28
+ initital={"instrument":cls.instrument ,"raw_data": LabelValue(raw_data_name)},
29
+ )
30
+
31
+ # Date
32
+ line = skip_lines_then_readline(raw_data_file, ROW_FILEOPENTIME-1)
33
+ info = line.split(",")
34
+ try:
35
+ if info[0].strip() != "FILEOPENTIME":
36
+ return fail_signal
37
+ dateMM_DD_YYYY = info[2].split("/")
38
+ dateYYYYMMDD = f"{dateMM_DD_YYYY[2]}{dateMM_DD_YYYY[0].zfill(2)}{dateMM_DD_YYYY[1].zfill(2)}"
39
+ except IndexError:
40
+ return fail_signal
41
+ labels["date"] = LabelValue(dateYYYYMMDD)
42
+
43
+ # S1 Name
44
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE1_NAME - ROW_FILEOPENTIME -1)
45
+ info = line.split(",")
46
+ try:
47
+ if info[-1].strip() != "Sample1 Name":
48
+ return fail_signal
49
+ s1_name = info[1]
50
+ except IndexError:
51
+ return fail_signal
52
+ labels["sample1_name"] = LabelValue(s1_name)
53
+
54
+ # S1 Units
55
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE1_UNITS - ROW_SAMPLE1_NAME -1)
56
+ info = line.split(",")
57
+ try:
58
+ if info[-1].strip() != "Sample1 Units":
59
+ return fail_signal
60
+ s1_units = info[1]
61
+ except IndexError:
62
+ return fail_signal
63
+ labels["sample1_units"] = LabelValue(s1_units)
64
+
65
+ # S2 Name
66
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE2_NAME - ROW_SAMPLE1_UNITS -1)
67
+ info = line.split(",")
68
+ try:
69
+ if info[-1].strip() != "Sample2 Name":
70
+ return fail_signal
71
+ s2_name = info[1]
72
+ except IndexError:
73
+ return fail_signal
74
+ labels["sample2_name"] = LabelValue(s2_name)
75
+
76
+ # S2 Units
77
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE2_UNITS - ROW_SAMPLE2_NAME -1)
78
+ info = line.split(",")
79
+ try:
80
+ if info[-1].strip() != "Sample2 Units":
81
+ return fail_signal
82
+ s2_units = info[1]
83
+ except IndexError:
84
+ return fail_signal
85
+ labels["sample2_units"] = LabelValue(s2_units)
86
+
87
+ # S3 Name
88
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE3_NAME - ROW_SAMPLE2_UNITS -1)
89
+ info = line.split(",")
90
+ try:
91
+ if info[-1].strip() != "Sample3 Name":
92
+ return fail_signal
93
+ s3_name = info[1]
94
+ except IndexError:
95
+ return fail_signal
96
+ labels["sample3_name"] = LabelValue(s3_name)
97
+
98
+ # S3 Units
99
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE3_UNITS - ROW_SAMPLE3_NAME -1)
100
+ info = line.split(",")
101
+ try:
102
+ if info[-1].strip() != "Sample3 Units":
103
+ return fail_signal
104
+ s3_units = info[1]
105
+ except IndexError:
106
+ return fail_signal
107
+ labels["sample3_units"] = LabelValue(s3_units)
108
+
109
+ # Headers
110
+ line = skip_lines_then_readline(raw_data_file,ROW_HEADERS - ROW_SAMPLE3_UNITS -1)
111
+ all_headers = dict((header,i) for (i,header) in enumerate(line.split(",")))
112
+ headers = (T, H, R1, I1, R2, I2, R3, I3)
113
+ try:
114
+ indexs = tuple(all_headers[header] for header in headers)
115
+ except KeyError:
116
+ return fail_signal
117
+
118
+ # read data
119
+ points = []
120
+ for line in raw_data_file:
121
+ cells = line.split(",")
122
+ points.append([np.float64(cells[i]) if cells[i] else np.nan for i in indexs])
123
+
124
+ return Data(
125
+ labels=labels,
126
+ _headers=headers,
127
+ points=np.array(points),
128
+ )
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ from ..base import *
4
+ from ..utils import skip_lines_then_readline
5
+
6
+ ROW_FILEOPENTIME = 5
7
+ ROW_SAMPLE_MATERIAL = 18
8
+ ROW_SAMPLE_COMMENT = 19
9
+ ROW_SAMPLE_MASS = 20
10
+ ROW_SAMPLE_HOLDER = 25
11
+ ROW_HEADERS = 36
12
+
13
+ T = "Temperature (K)"
14
+ H = "Magnetic Field (Oe)"
15
+ M = "DC Moment Free Ctr (emu)"
16
+
17
+ Mfit = "DC Free Fit"
18
+ Mfix = "DC Moment Fixed Ctr (emu)"
19
+ SD = "DC Squid Drift"
20
+ CP = "Center Position (mm)"
21
+ DCC = "DC Calculated Center (mm)"
22
+
23
+
24
+ headers = (T, H, M, Mfix, Mfit, SD, CP, DCC)
25
+
26
+ class VSM:
27
+ instrument = "VSM"
28
+
29
+ @classmethod
30
+ def fetch_raw_data(cls, raw_data_file:TextIO, raw_data_name:str)->Data|FAIL:
31
+ labels = LabelDict(
32
+ initital={"instrument":cls.instrument ,"raw_data": LabelValue(raw_data_name)},
33
+ )
34
+
35
+ # Date
36
+ line = skip_lines_then_readline(raw_data_file, ROW_FILEOPENTIME-1)
37
+ info = line.split(",")
38
+ try:
39
+ if info[0].strip() != "FILEOPENTIME":
40
+ return fail_signal
41
+ dateMM_DD_YYYY = info[2].split("/")
42
+ dateYYYYMMDD = f"{dateMM_DD_YYYY[2]}{dateMM_DD_YYYY[0].zfill(2)}{dateMM_DD_YYYY[1].zfill(2)}"
43
+ except IndexError:
44
+ return fail_signal
45
+ labels["date"] = LabelValue(dateYYYYMMDD)
46
+
47
+ # Material
48
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE_MATERIAL - ROW_FILEOPENTIME -1)
49
+ info = line.split(",")
50
+ try:
51
+ if info[-1].strip() != "SAMPLE_MATERIAL":
52
+ return fail_signal
53
+ material = info[1]
54
+ except IndexError:
55
+ return fail_signal
56
+ labels["material"] = LabelValue(material)
57
+
58
+ # COMMENT
59
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE_COMMENT - ROW_SAMPLE_MATERIAL -1)
60
+ info = line.split(",")
61
+ try:
62
+ if info[-1].strip() != "SAMPLE_COMMENT":
63
+ return fail_signal
64
+ comment = info[1]
65
+ except IndexError:
66
+ return fail_signal
67
+ labels["comment"] = LabelValue(comment)
68
+
69
+ # Mass
70
+ line = skip_lines_then_readline(raw_data_file, ROW_SAMPLE_MASS - ROW_SAMPLE_COMMENT -1)
71
+ info = line.split(",")
72
+ try:
73
+ if info[-1].strip() != "SAMPLE_MASS":
74
+ return fail_signal
75
+ mass = info[1]
76
+ except IndexError:
77
+ return fail_signal
78
+ labels["mass"] = LabelValue(mass,"mg")
79
+
80
+ # Sample Holder
81
+ line = skip_lines_then_readline(raw_data_file,ROW_SAMPLE_HOLDER - ROW_SAMPLE_MASS -1)
82
+ info = line.split(",")
83
+ try:
84
+ if info[-1].strip() != "SAMPLE_HOLDER":
85
+ return fail_signal
86
+ sample_holder = info[1]
87
+ except IndexError:
88
+ return None
89
+ labels["sample_holder"] = LabelValue(sample_holder)
90
+
91
+ # Headers
92
+ line = skip_lines_then_readline(raw_data_file,ROW_HEADERS - ROW_SAMPLE_HOLDER -1)
93
+ all_headers = dict((header,i) for (i,header) in enumerate(line.split(",")))
94
+ try:
95
+ indexs = tuple(all_headers[header] for header in headers)
96
+ except KeyError:
97
+ return fail_signal
98
+
99
+ # read data
100
+ points = []
101
+ for line in raw_data_file:
102
+ cells = line.split(",")
103
+ points.append(tuple(float(cells[i]) for i in indexs))
104
+
105
+ return Data(
106
+ labels=labels,
107
+ _headers=headers,
108
+ points=np.array(points),
109
+ )