labmate 0.8.3__py3-none-any.whl → 0.9.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.
labmate/__config__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """This file contains all package constants."""
2
2
 
3
- __version__ = "0.8.3"
3
+ __version__ = "0.9.0"
@@ -1,9 +1,10 @@
1
1
  """Module that contains NotebookAcquisitionData class."""
2
+
2
3
  from typing import Dict, List, Optional, Union
3
4
 
4
5
  from dh5 import DH5
5
- from ..utils.file_read import read_files
6
6
 
7
+ from ..utils.file_read import read_files
7
8
  from .logger_setup import logger
8
9
 
9
10
 
@@ -14,6 +15,9 @@ class NotebookAcquisitionData(DH5):
14
15
  `cell` is a str. It saves using `save_cell` function that will save it to `..._CELL.py` file
15
16
  """
16
17
 
18
+ _current_step: int
19
+ _cells: Dict[int, Optional[str]]
20
+
17
21
  def __init__(
18
22
  self,
19
23
  filepath: str,
@@ -58,10 +62,11 @@ class NotebookAcquisitionData(DH5):
58
62
  self._config = configs
59
63
  self.save_configs()
60
64
 
61
- self._cell = cell
62
- self.save_cell(cell=cell)
65
+ self._cells = {1: cell}
66
+ self.save_cell(cell=cell, suffix="1")
63
67
 
64
68
  self.experiment_name = experiment_name
69
+ self.current_step = 1
65
70
 
66
71
  self["useful"] = False
67
72
 
@@ -95,7 +100,12 @@ class NotebookAcquisitionData(DH5):
95
100
  with open(filepath + "_" + name, "w", encoding="utf-8") as file:
96
101
  file.write(value)
97
102
 
98
- def save_cell(self, cell: Optional[str] = None, filepath: Optional[str] = None):
103
+ def save_cell(
104
+ self,
105
+ cell: Optional[str] = None,
106
+ filepath: Optional[str] = None,
107
+ suffix: Optional[str] = None,
108
+ ):
99
109
  """Save the cell code to the h5 file and possibly to a file.
100
110
 
101
111
  If `save_files` during init was set to True, then it will create a '.py' file near
@@ -108,14 +118,15 @@ class NotebookAcquisitionData(DH5):
108
118
  the desired location, i.e. it should end with the file prefix to which the suffix and
109
119
  'py' extension will be added. Defaults to save filepath as h5 file.
110
120
  """
111
- cell = cell or self._cell
112
121
  if cell == "none":
113
122
  return
114
123
  if cell is None or cell == "":
115
124
  logger.warning("Acquisition cell is not set. Nothing to save")
116
125
  return
117
-
118
- self["acquisition_cell"] = cell
126
+ if suffix is not None:
127
+ self[f"acquisition_cell/{suffix}"] = cell
128
+ else:
129
+ self["acquisition_cell/0"] = cell
119
130
 
120
131
  if not self._save_files:
121
132
  return
@@ -124,13 +135,26 @@ class NotebookAcquisitionData(DH5):
124
135
  with open(filepath + "_CELL.py", "w", encoding="utf-8") as file:
125
136
  file.write(cell)
126
137
 
138
+ def save_cells(
139
+ self,
140
+ cells: Optional[Dict[int, Optional[str]]] = None,
141
+ filepath: Optional[str] = None,
142
+ ):
143
+ """Save all sells that are provided or pushed into self._cells array."""
144
+ cells = cells or self._cells
145
+ # if len(cells) == 1:
146
+ # self.save_cell(cells.popitem()[1], filepath)
147
+ # return
148
+ for i, cell in cells.items():
149
+ self.save_cell(cell, filepath, suffix=str(i))
150
+
127
151
  def save_additional_info(self):
128
152
  """Save all additional information, i.e. cell code, configs. Put useful key to True."""
129
153
  self["useful"] = True
130
154
 
131
155
  if not self._save_files:
132
156
  return
133
- self.save_cell()
157
+ self.save_cells()
134
158
  self.save_configs()
135
159
 
136
160
  def save_acquisition(self, **kwds) -> "NotebookAcquisitionData":
@@ -140,3 +164,18 @@ class NotebookAcquisitionData(DH5):
140
164
  if self.save_on_edit is False:
141
165
  self.save()
142
166
  return self
167
+
168
+ @property
169
+ def current_step(self):
170
+ """Return the current step of the acquisition."""
171
+ return self._current_step
172
+
173
+ @current_step.setter
174
+ def current_step(self, value: int):
175
+ self._current_step = value
176
+
177
+ def set_cell(self, cell: Optional[str], step: Optional[int] = None):
178
+ """Set the cell code."""
179
+ if step is None:
180
+ step = self.current_step
181
+ self._cells[step] = cell
@@ -185,9 +185,7 @@ class AcquisitionLoop(DH5):
185
185
  )
186
186
  self[key][iteration] = value
187
187
  else:
188
- if isinstance(value, (complex, np.complex_)) or ( # type: ignore
189
- isinstance(value, (np.ndarray,)) and value.dtype == np.complex_
190
- ):
188
+ if np.iscomplexobj(value):
191
189
  self[key] = SyncNp(np.zeros(key_shape, dtype=np.complex128))
192
190
  else:
193
191
  self[key] = SyncNp(np.zeros(key_shape))
@@ -132,6 +132,9 @@ class AcquisitionManager:
132
132
  filename = [str(filename)]
133
133
 
134
134
  self.config_files = list(filename)
135
+ self._config_files_names_to_path = {
136
+ os.path.basename(file): file for file in self.config_files
137
+ }
135
138
 
136
139
  for config_file in self.config_files:
137
140
  if not os.path.exists(config_file):
@@ -1,10 +1,12 @@
1
1
  """AnalysisData class."""
2
2
 
3
+ import json
3
4
  import os
4
- from typing import List, Literal, Optional, Protocol, Tuple, TypeVar, Union
5
+ from typing import List, Literal, Optional, Protocol, Tuple, TypedDict, TypeVar, Union
5
6
 
6
7
  from dh5 import DH5
7
8
  from dh5.path import Path
9
+ from matplotlib.backends.backend_pdf import PdfPages
8
10
 
9
11
  from .. import utils
10
12
  from .analysis_loop import AnalysisLoop
@@ -21,6 +23,13 @@ class FigureProtocol(Protocol):
21
23
  """Save the figure to a file."""
22
24
 
23
25
 
26
+ class PdfMetadataDict(TypedDict, total=False):
27
+ """Metadata for the pdf file."""
28
+
29
+ Subject: Union[str, dict]
30
+ Keywords: Union[str, dict]
31
+
32
+
24
33
  class AnalysisData(DH5):
25
34
  """A subclass of DH5 that provides additional functionality for analyzing data.
26
35
 
@@ -167,6 +176,7 @@ class AnalysisData(DH5):
167
176
  name: Optional[Union[str, int]] = None,
168
177
  extensions: Optional[str] = None,
169
178
  tight_layout: bool = True,
179
+ metadata: Optional[PdfMetadataDict] = None,
170
180
  **kwargs,
171
181
  ) -> _T:
172
182
  """Save the figure with the filename (...)_FIG_name.
@@ -201,7 +211,23 @@ class AnalysisData(DH5):
201
211
  )
202
212
  if tight_layout and hasattr(fig, "tight_layout"):
203
213
  fig.tight_layout() # type: ignore
204
- fig.savefig(full_fig_name, **kwargs)
214
+ if metadata is None:
215
+ fig.savefig(full_fig_name, **kwargs)
216
+ else:
217
+ if not full_fig_name.endswith(".pdf"):
218
+ raise ValueError("Metadata can be added only to pdf files.")
219
+ pdf_fig = PdfPages(full_fig_name)
220
+ fig.savefig(pdf_fig, format="pdf", **kwargs) # type: ignore
221
+ metadata = metadata or {}
222
+ if not isinstance(metadata.get("Subject", ""), str):
223
+ metadata["Subject"] = json.dumps(metadata.get("Subject"))
224
+
225
+ if not isinstance(metadata.get("Keywords", ""), str):
226
+ metadata["Keywords"] = json.dumps(metadata.get("Keywords"))
227
+
228
+ pdf_metadata = pdf_fig.infodict()
229
+ pdf_metadata.update(metadata)
230
+ pdf_fig.close()
205
231
 
206
232
  self._figure_saved = True
207
233
 
@@ -290,6 +290,7 @@ class AcquisitionAnalysisManager(AcquisitionManager):
290
290
  cell: Optional[str] = None,
291
291
  prerun: Optional[Union[_CallableWithNoArgs, List[_CallableWithNoArgs]]] = None,
292
292
  save_on_edit: Optional[bool] = None,
293
+ step: int = 1,
293
294
  ) -> "AcquisitionAnalysisManager":
294
295
  self._analysis_cell_str = None
295
296
  self._analysis_data = None
@@ -297,12 +298,25 @@ class AcquisitionAnalysisManager(AcquisitionManager):
297
298
  self._acquisition_started = time.time()
298
299
 
299
300
  cell = cell or get_current_cell(self.shell)
300
-
301
- self.new_acquisition(name=name, cell=cell, save_on_edit=save_on_edit)
301
+ if step != 1 and self._current_acquisition is not None:
302
+ if self._current_acquisition.experiment_name != name:
303
+ raise ValueError(
304
+ f"Current acquisition ('{self.current_experiment_name}') "
305
+ f"isn't the one expected ('{name}') for this acquisition. "
306
+ f"Possible solutions: run acquisition '{name}' with step 1; "
307
+ f"or change current acquisition name to '{self.current_experiment_name}'"
308
+ )
309
+ self._current_acquisition.current_step = step
310
+ self._current_acquisition.set_cell(cell, step=step)
311
+ self._current_acquisition.save_cell(cell, suffix=str(step))
312
+ else:
313
+ self.new_acquisition(name=name, cell=cell, save_on_edit=save_on_edit)
302
314
 
303
315
  logger.info(self.current_filepath.basename)
304
316
 
305
- utils.run_functions(self._acquisition_cell_prerun_hook)
317
+ if step == 1:
318
+ utils.run_functions(self._acquisition_cell_prerun_hook)
319
+
306
320
  utils.run_functions(prerun)
307
321
 
308
322
  return self
@@ -528,10 +542,62 @@ class AcquisitionAnalysisManager(AcquisitionManager):
528
542
  )
529
543
  continue
530
544
  file, line_no = res
545
+ file = self._config_files_names_to_path.get(file, file)
531
546
  link = display.links.create_link(param_text, file, line_no, after_text)
532
547
  links += link + "<br/>"
533
548
  return display.display_html(links)
534
549
 
550
+ def display_cfg_link(
551
+ self,
552
+ parameters: Dict[str, Any],
553
+ ):
554
+ from labmate.display import html_output
555
+
556
+ links = []
557
+ for param, value in parameters.items():
558
+ param_eq = f"{param.strip()} = "
559
+ res = self.find_param_in_config(param_eq)
560
+ if res is None:
561
+ logger.warning(
562
+ "Parameter '%s' cannot be found in default config files.", param
563
+ )
564
+ continue
565
+ file, line_no = res
566
+ file = self._config_files_names_to_path.get(file, file)
567
+
568
+ def update_value(param, value):
569
+ self.update_config_params_on_disk({param: value})
570
+
571
+ buttons = [
572
+ display.buttons.create_button(update_value, param, value, name="Update")
573
+ ]
574
+
575
+ link = html_output.create_link_row(
576
+ link_text=f"{param} = ",
577
+ link_url=f"{file}:{line_no}",
578
+ text=str(value),
579
+ buttons=buttons, # type: ignore
580
+ )
581
+ links.append(link)
582
+ return display.display_widgets_vertically(links, class_="labmate-params")
583
+
584
+ def update_config_params_on_disk(self, params: Dict[str, Any]):
585
+ # params_per_files = {}
586
+ # for param, value in params.items():
587
+ # res = self.find_param_in_data_config(param)
588
+ # if res is None:
589
+ # raise ValueError(
590
+ # f"Parameter '{param}' cannot be found in default config files."
591
+ # )
592
+ # file, _ = res
593
+ # params_per_files.setdefault(file, {})[param] = value
594
+
595
+ for file in self.config_files:
596
+ file = self._config_files_names_to_path.get(file, file)
597
+ utils.file_read.update_file_variable(file, params)
598
+
599
+ return self
600
+
535
601
  def connect_default_widget(
536
602
  self,
537
603
  objs: Union[
@@ -1,20 +1,38 @@
1
- """Module with widgets that can be displayed with AcquisitionAnalysisManager."""
2
-
3
1
  import os
4
2
  from typing import TYPE_CHECKING, List, Optional, Protocol, TypeVar
5
3
 
6
4
  from .. import display as lm_display
7
5
  from ..display import platform_utils
8
6
 
9
- # import abc
10
-
11
-
12
7
  if TYPE_CHECKING:
13
8
  from labmate.acquisition_notebook import AcquisitionAnalysisManager
14
9
 
15
10
  _T = TypeVar("_T")
16
11
 
17
12
 
13
+ def _get_filepath(aqm: "AcquisitionAnalysisManager") -> Optional[str]:
14
+ filepath = aqm.current_analysis or aqm.current_acquisition
15
+ return filepath.filepath if filepath else None
16
+
17
+
18
+ def _create_file_link(aqm: "AcquisitionAnalysisManager", level_up) -> str:
19
+ filepath = _get_filepath(aqm)
20
+ if filepath is None:
21
+ return ""
22
+ link_name = os.path.basename(filepath)
23
+ link = "/".join(
24
+ os.path.abspath(filepath).replace("\\", "/").split("/")[-level_up:]
25
+ ).replace(" ", "%20")
26
+ link = f"[{link_name}](//kyrylo-gr.github.io/h5viewer/open?url={link})"
27
+ return link
28
+
29
+
30
+ def display_widgets(objs: List["WidgetProtocol"], *args, **kwargs):
31
+ """Create (with *args, **kwargs) and display a list of widgets."""
32
+ widgets = [obj.create(*args, **kwargs) for obj in objs]
33
+ lm_display.display_widgets(widgets) # type: ignore
34
+
35
+
18
36
  class WidgetProtocol(Protocol):
19
37
  """Protocol for widgets that can be displayed with AcquisitionAnalysisManager.
20
38
 
@@ -73,7 +91,7 @@ class BaseWidget:
73
91
  raise NotImplementedError("This method is not implemented for the base class.")
74
92
 
75
93
 
76
- class CopyFilePathButton(BaseWidget):
94
+ class CopyFileURLPathButton(BaseWidget):
77
95
  """Create button to copy file path to clipboard.
78
96
 
79
97
  Examples:
@@ -180,32 +198,9 @@ class OpenFinderButton(BaseWidget):
180
198
  path = path.replace("/", "\\")
181
199
  subprocess.run(["explorer", "/select,", path], shell=True)
182
200
  elif sys.platform == "darwin":
183
- subprocess.Popen(["open", "-R", path])
201
+ subprocess.Popen(["open", "-R", os.path.dirname(path)])
184
202
  else:
185
- subprocess.Popen(["nautilus", "--select", path])
203
+ subprocess.Popen(["nautilus", "--select", os.path.dirname(path)])
186
204
 
187
205
  self.widget = lm_display.buttons.create_button(open_finder, name="Open finder")
188
206
  return self.widget
189
-
190
-
191
- def _get_filepath(aqm: "AcquisitionAnalysisManager") -> Optional[str]:
192
- filepath = aqm.current_analysis or aqm.current_acquisition
193
- return filepath.filepath if filepath else None
194
-
195
-
196
- def _create_file_link(aqm: "AcquisitionAnalysisManager", level_up) -> str:
197
- filepath = _get_filepath(aqm)
198
- if filepath is None:
199
- return ""
200
- link_name = os.path.basename(filepath)
201
- link = "/".join(
202
- os.path.abspath(filepath).replace("\\", "/").split("/")[-level_up:]
203
- ).replace(" ", "%20")
204
- link = f"[{link_name}](//kyrylo-gr.github.io/h5viewer/open?url={link})"
205
- return link
206
-
207
-
208
- def display_widgets(objs: List["WidgetProtocol"], *args, **kwargs):
209
- """Create (with *args, **kwargs) and display a list of widgets."""
210
- widgets = [obj.create(*args, **kwargs) for obj in objs]
211
- lm_display.display_widgets(widgets) # type: ignore
@@ -2,9 +2,15 @@
2
2
  import importlib
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
- from .main import display, display_html, display_widgets, logger
5
+ from .main import (
6
+ display,
7
+ display_html,
8
+ display_widgets,
9
+ display_widgets_vertically,
10
+ logger,
11
+ )
6
12
 
7
- __all__ = ["links", "buttons", "logger"]
13
+ __all__ = ["links", "buttons", "logger", "html_output"]
8
14
 
9
15
 
10
16
  class _LazyModule:
@@ -28,7 +34,8 @@ class _LazyModule:
28
34
 
29
35
  links = _LazyModule("links")
30
36
  buttons = _LazyModule("buttons")
37
+ html_output = _LazyModule("html_output")
31
38
 
32
39
 
33
40
  if TYPE_CHECKING:
34
- from . import buttons, links
41
+ from . import buttons, html_output, links
@@ -1,5 +1,7 @@
1
1
  """This submodule contains functions that create Button widgets."""
2
+
2
3
  from typing import TypeVar
4
+
3
5
  from .main import display, widgets
4
6
 
5
7
  # from functools import wraps
@@ -69,6 +71,8 @@ def create_button(func, *args, name=None, **kwargs) -> "DisplayingButton":
69
71
  def on_button_click(_):
70
72
  func(*args, **kwargs)
71
73
 
74
+ button.style.button_color = "transparent" # type: ignore
75
+
72
76
  button.on_click(on_button_click)
73
77
  return button
74
78
 
@@ -1,5 +1,9 @@
1
1
  """This submodule contains functions that create different html."""
2
2
 
3
+ from typing import List, Optional
4
+
5
+ from .main import widgets
6
+
3
7
 
4
8
  def display_warning(text: str):
5
9
  """Display div warning block with `text`.
@@ -13,3 +17,47 @@ def display_warning(text: str):
13
17
  >{text}</div>"""
14
18
 
15
19
  display_html(str(html))
20
+
21
+
22
+ def create_link_row(
23
+ link_text: str,
24
+ link_url: str,
25
+ text: str,
26
+ buttons: Optional[List[widgets.Button]] = None,
27
+ ):
28
+
29
+ # Create the HTML link with custom styling
30
+ link_widget = widgets.HTML(
31
+ value=f'<a href="{link_url}" target="_blank" onclick="return false;">{link_text}</a>'
32
+ )
33
+
34
+ # Create the text with custom styling
35
+ text_widget = widgets.HTML(
36
+ value=f'<span style="padding: 0 10px;">{text}</span>',
37
+ layout=widgets.Layout(background_color="transparent"),
38
+ )
39
+
40
+ custom_css = """
41
+ <style>
42
+ .cell-output-ipywidget-background:has(.labmate-params) {
43
+ background: transparent !important;
44
+ }
45
+ .labmate-params, .labmate-params * {
46
+ color: inherit !important;
47
+ }
48
+ .labmate-params a {
49
+ text-decoration: underline;
50
+ color: blue;
51
+
52
+ }
53
+
54
+ </style>
55
+ """
56
+ buttons = buttons or []
57
+
58
+ hbox = widgets.HBox(
59
+ [widgets.HTML(custom_css), link_widget, text_widget, *buttons],
60
+ layout=widgets.Layout(background_color="transparent"),
61
+ )
62
+ hbox.add_class("labmate-params")
63
+ return hbox
labmate/display/main.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """This submodule contains functions that help to display content in IPython."""
2
+
2
3
  import logging
3
4
  import sys
4
5
  from typing import Callable, List
5
6
 
6
-
7
7
  logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.WARNING)
8
8
  logger = logging.getLogger(__name__)
9
9
  handler = logging.StreamHandler()
@@ -18,9 +18,9 @@ try:
18
18
  if "pytest" in sys.modules:
19
19
  raise ImportError
20
20
 
21
- from IPython.core.display import HTML # type: ignore
22
- from IPython.core import display # type: ignore
23
21
  import ipywidgets as widgets # pylint: disable=W0611 # type: ignore
22
+ from IPython.core import display # type: ignore
23
+ from IPython.core.display import HTML # type: ignore
24
24
 
25
25
  display = display.display_functions.display
26
26
 
@@ -49,9 +49,17 @@ except ImportError:
49
49
  def __init__(self, lst: list) -> None: # noqa: D107
50
50
  pass
51
51
 
52
+ class VBox: # noqa: D106
53
+ def __init__(self, lst: list) -> None: # noqa: D107
54
+ pass
55
+
52
56
  class CoreWidget: # noqa: D106
53
57
  pass
54
58
 
59
+ class Layout:
60
+ def __init__(self, *args, **kwargs) -> None:
61
+ del args, kwargs
62
+
55
63
  # pylint: enable=C0115, C0103, R0903
56
64
 
57
65
 
@@ -64,3 +72,11 @@ def display_widgets(objs: List[widgets.CoreWidget]):
64
72
  """Display the given list of widgets in a HBox ."""
65
73
  button_row = widgets.HBox(objs)
66
74
  display(button_row)
75
+
76
+
77
+ def display_widgets_vertically(objs: List[widgets.CoreWidget], class_: str = ""):
78
+ """Display the given list of widgets in a VBox ."""
79
+ button_row = widgets.VBox(objs)
80
+ if class_:
81
+ button_row.add_class(class_) # type: ignore
82
+ display(button_row)
labmate/utils/__init__.py CHANGED
@@ -1,2 +1,3 @@
1
- from .random_utils import * # noqa
1
+ from . import file_read # noqa
2
2
  from . import title_parsing # noqa
3
+ from .random_utils import * # noqa
@@ -1,6 +1,10 @@
1
1
  """Different method to read files."""
2
+
3
+ import json
2
4
  import os
3
- from typing import Dict, List
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from ..parsing.brackets_score import BracketsScore
4
8
 
5
9
 
6
10
  def read_file(file: str, /) -> str:
@@ -47,3 +51,46 @@ def read_files(files: List[str], /) -> Dict[str, str]:
47
51
  )
48
52
  configs[config_file_name] = read_file(config_file)
49
53
  return configs
54
+
55
+
56
+ def update_file_variable(file, params: Dict[str, Any]):
57
+ """
58
+ Update the variables in a file with the given parameters.
59
+
60
+ Args:
61
+ file (str): The path to the file to update.
62
+ params (Dict[str, Any]): The parameters to update the file with.
63
+ """
64
+ with open(file, "r", encoding="utf-8") as file_opened:
65
+ lines = file_opened.readlines()
66
+ brackets = BracketsScore()
67
+ current_param: Optional[str] = None
68
+ # print(lines)
69
+ for line in lines:
70
+ # print(line)
71
+ if len(line) == 0:
72
+ continue
73
+ if brackets.is_zero() and "=" in line:
74
+ param = line.split("=")[0].strip()
75
+ # print("param", param)
76
+ if param in params:
77
+ current_param = param
78
+ start_line = lines.index(line)
79
+ brackets.update_from_str(line)
80
+ if brackets.is_zero() and current_param is not None:
81
+ end_line = lines.index(line)
82
+ end_comment = line.split("#")[-1].strip() if "#" in line else None
83
+ del lines[start_line : end_line + 1]
84
+ value_str = json.JSONEncoder().encode(params[current_param])
85
+ lines.insert(
86
+ start_line,
87
+ (
88
+ f"{current_param} = {value_str}"
89
+ + (f" # {end_comment}" if end_comment is not None else "")
90
+ + "\n"
91
+ ),
92
+ )
93
+ current_param = None
94
+
95
+ with open(file, "w", encoding="utf-8") as file_opened:
96
+ file_opened.writelines(lines)
@@ -17,7 +17,11 @@ def parse_get_format(key: str) -> Tuple[str, Optional[str], Optional[str]]:
17
17
  args = key.split("__")
18
18
  if len(args) >= 3:
19
19
  return args[0], args[1], args[2]
20
- elif len(args) == 2 and len(args[1]) > 0 and args[1][0].isdigit():
20
+ elif (
21
+ len(args) == 2
22
+ and len(args[1]) > 0
23
+ and (args[1][0].isdigit() or args[1][0] in (".", "_"))
24
+ ):
21
25
  return args[0], None, args[1]
22
26
  elif len(args) == 2:
23
27
  return args[0], args[1], None
@@ -32,6 +36,23 @@ class ValueForPrint(NamedTuple):
32
36
  units: Optional[str] = None
33
37
  format: Optional[str] = None
34
38
 
39
+ def format_value(self, format_spec: Optional[str] = None) -> str:
40
+ format_spec = format_spec or self.format
41
+ if not format_spec:
42
+ return str(self.value)
43
+ if format_spec.endswith("p"):
44
+ format_spec = format_spec[:-1] + "e"
45
+ value_str = format(self.value, format_spec)
46
+ number, power = value_str.split("e")
47
+ number = number.rstrip("0_").rstrip(".") if "." in number else number
48
+ power = (
49
+ (power[0].lstrip("+0") + power[1:].lstrip("+0"))
50
+ if len(power) > 1
51
+ else power
52
+ )
53
+ return f"{number}e{power}"
54
+ return format(self.value, format_spec)
55
+
35
56
 
36
57
  def format_title(values: List[ValueForPrint], max_length: Optional[int] = None) -> str:
37
58
  """Create title out of a list of valuesForPrint.
@@ -47,11 +68,7 @@ def format_title(values: List[ValueForPrint], max_length: Optional[int] = None)
47
68
  last_line_len = 0
48
69
  for value in values:
49
70
  units = f" ({value.units})" if value.units is not None else ""
50
- value_str = (
51
- value.value
52
- if value.format is None
53
- else value.value.__format__(f".{value.format}")
54
- )
71
+ value_str = value.format_value()
55
72
  new_txt = f"{value.key} = {value_str}{units}"
56
73
  if not max_length or (
57
74
  (last_line_len + len(new_txt) < max_length) or last_line_len == 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: labmate
3
- Version: 0.8.3
3
+ Version: 0.9.0
4
4
  Summary: Data management library to save data and plots to hdf5 files
5
5
  Home-page: https://github.com/kyrylo-gr/labmate
6
6
  Author: kyrylo.gr | LKB-OMQ
@@ -1,40 +1,40 @@
1
- labmate/__config__.py,sha256=SjpU4iAgKRHNr6dEbjqw0YR8l6kjHwceS5UAD_5Gd2I,71
1
+ labmate/__config__.py,sha256=PHXqCXDgKKdHZFBttZvsptpfNnsf6yrRAF8OawXf4aE,71
2
2
  labmate/__init__.py,sha256=aHQiPLldCXIJqz6wwcfyFU9sGLofUR3W5sXBIRzK2n4,182
3
3
  labmate/acquisition/__init__.py,sha256=8q3dy18lL32A9y_Du8GggpLgJqDMFcFKddHrySMavrM,269
4
- labmate/acquisition/acquisition_data.py,sha256=Ic-rf8VKs9erjv1wQ8txvdlSRK87ar5Z0TUVvOrl_Bw,5534
5
- labmate/acquisition/acquisition_loop.py,sha256=ldp9Hoz9C3x89FFYiHasezOYooKto7x2MaYUjlz-NrE,11240
6
- labmate/acquisition/acquisition_manager.py,sha256=rKDUgR-IbQ0dzS728DtZKGkksanhrgeYduk4AmqRX0E,10370
7
- labmate/acquisition/analysis_data.py,sha256=DF9NshXBWu74gK8b_ZOGIuveabkzbLIDXfyui1c4yK8,14695
4
+ labmate/acquisition/acquisition_data.py,sha256=FUF0JTHl5_nmgPHUHFocTQEwPlxuCAVT5Xsf338jVhI,6686
5
+ labmate/acquisition/acquisition_loop.py,sha256=fiiseV21GB7pczHSEJrlLPiTSQ2u9y5VMDyCO7Hn0vY,11106
6
+ labmate/acquisition/acquisition_manager.py,sha256=uzGCWKCcOIwKTZsKp12OY1rvyWaFBlBsBDRR7mFZwj0,10496
7
+ labmate/acquisition/analysis_data.py,sha256=PGkRAQU0GBCe70Cjya_qRiwXNT2B1rd_Gcg8AESz6MI,15702
8
8
  labmate/acquisition/analysis_loop.py,sha256=1Y8lyPkTCNwskM8DkwrMXSOt0hNmBHcWJaQjdVZ81Hs,5075
9
9
  labmate/acquisition/config_file.py,sha256=1WwqaKTM-R5xZQHqqqGUi2QCF0PC1ag2mFgPOJuEdWI,2212
10
10
  labmate/acquisition/custom_lint.py,sha256=x4vNoOnbH3A4Odu2DQVtBsuSPo5JfvRpo8_EP0EOmgM,1005
11
11
  labmate/acquisition/logger_setup.py,sha256=udTp-0S4cqhGdUGQlk3G3Eg51wePEGraNG-69P9fTOo,129
12
12
  labmate/acquisition_notebook/__init__.py,sha256=ZtOGQtmPqEM1IRrL-_JYo4xYA87lFQ5JY5GmKcZz9z0,251
13
- labmate/acquisition_notebook/acquisition_analysis_manager.py,sha256=UJR5oH4AtUMlh6BOUUxmbUvDXggN08wppOGind07iV0,19286
14
- labmate/acquisition_notebook/display_widget.py,sha256=VNSo7r5nY_8biq-PAJeLvU_nzJ71kS82gFuOy-762BQ,6918
13
+ labmate/acquisition_notebook/acquisition_analysis_manager.py,sha256=ZxnKvwl4jxkqQKPdgoYsJ0NKxKPcrgi_PXkeqCdO2lY,21967
14
+ labmate/acquisition_notebook/display_widget.py,sha256=yS7KeZ362djhHYFldQMpz8keVT7G2MgHsCmGMlXT81s,6858
15
15
  labmate/attrdict/__init__.py,sha256=MvuZVe7j4a0HxGMxim_K2cv-dhqZOfzdeMiTX-SRgDg,58
16
16
  labmate/attrdict/attrdict_class.py,sha256=4lKXe7oZo_lLHefmf5vAOKhibWgGDffJcxMhaWLvGs4,4047
17
- labmate/display/__init__.py,sha256=tD9kCR-2VpV2Fvm7cpw4mDgqMCEXIXh_AOO5KLawxEo,844
18
- labmate/display/buttons.py,sha256=_WbtS5PVpjgH2UEaSM5MACGHqcWiCSV3xN8di-MI7hk,2861
19
- labmate/display/html_output.py,sha256=SRsrszxBxuv-sQaWEkW_fyxFbImyJVj76iMTysqxKa4,425
17
+ labmate/display/__init__.py,sha256=u6HL38LpkeiGDkcIqE4GVvsEGF8OgZ776cSOHG5dmLA,966
18
+ labmate/display/buttons.py,sha256=ReFLn5KD-KOlxjZPCoT32lt1jNMN8Xka3K35oh17ZLo,2926
19
+ labmate/display/html_output.py,sha256=aBDVzaR39PfFxchKcY9W5OpiYjIzB6t3WzNjzjubjzg,1591
20
20
  labmate/display/links.py,sha256=YgCNxowca-oHxqozGWbe4oGLzwltnZ-C0v3E5fQnK0U,746
21
- labmate/display/main.py,sha256=5M0-ckxhkCH0LXaZN6HD61b0noMypolKC9foEQmXx9w,1852
21
+ labmate/display/main.py,sha256=2bjLbRmK5H1PYhtoiyZHzisu6CyQ6AmzlFtEhFZkStg,2354
22
22
  labmate/display/platform_utils/__init__.py,sha256=GOWB9vXF-wxGZfdHQJlQWy-hy2V2KXUP3JwxgN91Fq4,1136
23
23
  labmate/display/platform_utils/windows_utils.py,sha256=4Z_avuJIZ_KoXkuRZOH2667t2wEljzNBMP6fbNDknuk,3268
24
24
  labmate/parsing/__init__.py,sha256=AHNB502jlm6PGd49_PJjvSxt97fxJeXnIfXYh8HV5x0,1312
25
25
  labmate/parsing/brackets_score.py,sha256=zzup7z6o57YUGeMr5FOSTo3nz9Z62s2omxqFV3M9MmI,988
26
26
  labmate/parsing/parsed_value.py,sha256=UYB_gCkV3DiFQRjcprnQUPw--FqYPqb3pii-PQsbHf0,5261
27
27
  labmate/parsing/saving.py,sha256=pwCdYI9shrpKyFceRPNbPcbEfJbNQ7Xj0AMsDOr5qLA,2548
28
- labmate/utils/__init__.py,sha256=PExO1TzVGwb9dXVZ733iML2H-ZFly-dksiHLgFU1xr4,72
28
+ labmate/utils/__init__.py,sha256=g9LSaSVDFC4Uo7hbdd66aT7QewuXNRheZ8PZlMrcsNw,104
29
29
  labmate/utils/async_utils.py,sha256=mSfmpF7I3M5KePkPtoS-OcuoCkFDHPKjf-RVF0P3R48,118
30
30
  labmate/utils/autoreload.py,sha256=wKi1GgWyRu1h101OguVRpO3zQXZ8qsFj-K-1P8PKuq8,305
31
31
  labmate/utils/errors.py,sha256=ly7-JQStTKmPiMuT0w3eXFw1O8-1kpTsqZT2jebpJ-I,140
32
- labmate/utils/file_read.py,sha256=DAalvKehwEqVZjWSPhKZm8Myh6gfFA5gGT6WM0dPmyw,1536
32
+ labmate/utils/file_read.py,sha256=mRHRIyqejtDlfLuPyafEYcfKfb4bac7zHbt-DW091Vg,3155
33
33
  labmate/utils/lint.py,sha256=7llJbZUAM-ikEpmU_ZzraqOwGUuJPgk1wAf3aYMJdxg,9312
34
34
  labmate/utils/random_utils.py,sha256=ZA3gK9P-eTcd_a3BTS_ZeJI5A0GM_GXL7X3yUqnPTO4,690
35
- labmate/utils/title_parsing.py,sha256=RpbiZwuKnpIq-X5S5iS90oD8iOqocWcI27MGuB4kTLU,1988
36
- labmate-0.8.3.dist-info/LICENCE,sha256=J9XIxdJExlWYZuxhhKtk4oYILvUz8-JM0y_leRQCKUE,7488
37
- labmate-0.8.3.dist-info/METADATA,sha256=3lNnMZu3K0M6DkQdjAkgwQ0a3s8vEJdJ7-7C7lj4yK4,3210
38
- labmate-0.8.3.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
39
- labmate-0.8.3.dist-info/top_level.txt,sha256=WWAn6t2zNWsp02gRq6f5cSsGebcs-4L6HBFk0XrcY0o,8
40
- labmate-0.8.3.dist-info/RECORD,,
35
+ labmate/utils/title_parsing.py,sha256=5csdqiD6w6pzyqRon38V2WeGA00CifSrMMtoWZmk0Ok,2644
36
+ labmate-0.9.0.dist-info/LICENCE,sha256=J9XIxdJExlWYZuxhhKtk4oYILvUz8-JM0y_leRQCKUE,7488
37
+ labmate-0.9.0.dist-info/METADATA,sha256=-vPTTXkgoQsBsrYXkYh4roIcY3kKQwKQUFIOYZrpb1A,3210
38
+ labmate-0.9.0.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
39
+ labmate-0.9.0.dist-info/top_level.txt,sha256=WWAn6t2zNWsp02gRq6f5cSsGebcs-4L6HBFk0XrcY0o,8
40
+ labmate-0.9.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5