batplot 1.3.5__tar.gz → 1.3.7__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 (32) hide show
  1. {batplot-1.3.5 → batplot-1.3.7}/PKG-INFO +4 -1
  2. {batplot-1.3.5 → batplot-1.3.7}/README.md +2 -0
  3. {batplot-1.3.5 → batplot-1.3.7}/batplot/__init__.py +1 -1
  4. {batplot-1.3.5 → batplot-1.3.7}/batplot/args.py +68 -6
  5. {batplot-1.3.5 → batplot-1.3.7}/batplot/batplot.py +17 -1
  6. {batplot-1.3.5 → batplot-1.3.7}/batplot/operando.py +20 -4
  7. {batplot-1.3.5 → batplot-1.3.7}/batplot/operando_ec_interactive.py +11 -1
  8. {batplot-1.3.5 → batplot-1.3.7}/batplot/readers.py +47 -6
  9. {batplot-1.3.5 → batplot-1.3.7}/batplot.egg-info/PKG-INFO +4 -1
  10. {batplot-1.3.5 → batplot-1.3.7}/batplot.egg-info/requires.txt +1 -0
  11. {batplot-1.3.5 → batplot-1.3.7}/pyproject.toml +3 -2
  12. {batplot-1.3.5 → batplot-1.3.7}/LICENSE +0 -0
  13. {batplot-1.3.5 → batplot-1.3.7}/batplot/batch.py +0 -0
  14. {batplot-1.3.5 → batplot-1.3.7}/batplot/batplot_new.py +0 -0
  15. {batplot-1.3.5 → batplot-1.3.7}/batplot/cif.py +0 -0
  16. {batplot-1.3.5 → batplot-1.3.7}/batplot/cli.py +0 -0
  17. {batplot-1.3.5 → batplot-1.3.7}/batplot/converters.py +0 -0
  18. {batplot-1.3.5 → batplot-1.3.7}/batplot/cpc_interactive.py +0 -0
  19. {batplot-1.3.5 → batplot-1.3.7}/batplot/electrochem_interactive.py +0 -0
  20. {batplot-1.3.5 → batplot-1.3.7}/batplot/interactive.py +0 -0
  21. {batplot-1.3.5 → batplot-1.3.7}/batplot/modes.py +0 -0
  22. {batplot-1.3.5 → batplot-1.3.7}/batplot/plotting.py +0 -0
  23. {batplot-1.3.5 → batplot-1.3.7}/batplot/session.py +0 -0
  24. {batplot-1.3.5 → batplot-1.3.7}/batplot/style.py +0 -0
  25. {batplot-1.3.5 → batplot-1.3.7}/batplot/ui.py +0 -0
  26. {batplot-1.3.5 → batplot-1.3.7}/batplot/utils.py +0 -0
  27. {batplot-1.3.5 → batplot-1.3.7}/batplot.egg-info/SOURCES.txt +0 -0
  28. {batplot-1.3.5 → batplot-1.3.7}/batplot.egg-info/dependency_links.txt +0 -0
  29. {batplot-1.3.5 → batplot-1.3.7}/batplot.egg-info/entry_points.txt +0 -0
  30. {batplot-1.3.5 → batplot-1.3.7}/batplot.egg-info/top_level.txt +0 -0
  31. {batplot-1.3.5 → batplot-1.3.7}/setup.cfg +0 -0
  32. {batplot-1.3.5 → batplot-1.3.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.3.5
3
+ Version: 1.3.7
4
4
  Summary: Interactive plotting for XRD, PDF, and XAS data (.xye, .xy, .qye, .dat, .csv, .gr, .nor, .chik, .chir)
5
5
  Author-email: Tian Dai <tianda@uio.no>
6
6
  License: MIT License
@@ -45,6 +45,7 @@ Description-Content-Type: text/markdown
45
45
  License-File: LICENSE
46
46
  Requires-Dist: numpy
47
47
  Requires-Dist: matplotlib
48
+ Requires-Dist: rich>=10.0.0
48
49
  Dynamic: license-file
49
50
 
50
51
  # batplot
@@ -179,3 +180,5 @@ Tian Dai (tianda@uio.no)
179
180
  University of Oslo
180
181
 
181
182
  **GitHub**: https://github.com/tiandai-chem/batplot
183
+
184
+ **Subscribe for Updates**: Join batplot-lab@kjemi.uio.no for updates, feature announcements, and community feedback
@@ -130,3 +130,5 @@ Tian Dai (tianda@uio.no)
130
130
  University of Oslo
131
131
 
132
132
  **GitHub**: https://github.com/tiandai-chem/batplot
133
+
134
+ **Subscribe for Updates**: Join batplot-lab@kjemi.uio.no for updates, feature announcements, and community feedback
@@ -1,5 +1,5 @@
1
1
  """batplot: Interactive plotting for battery data visualization."""
2
2
 
3
- __version__ = "1.3.1"
3
+ __version__ = "1.3.7"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -4,6 +4,64 @@ from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  import sys
7
+ import re
8
+
9
+ # Try to import rich for colored output
10
+ try:
11
+ from rich.console import Console
12
+ from rich.markup import escape
13
+ _console = Console()
14
+ _HAS_RICH = True
15
+ except ImportError:
16
+ _console = None
17
+ _HAS_RICH = False
18
+
19
+
20
+ def _colorize_help(text: str) -> str:
21
+ """Add colors to help text by highlighting flags and special elements.
22
+
23
+ Args:
24
+ text: Plain help text
25
+
26
+ Returns:
27
+ Text with rich markup for colored output
28
+ """
29
+ if not _HAS_RICH:
30
+ return text
31
+
32
+ # Escape any existing markup
33
+ text = escape(text)
34
+
35
+ # Color all flags (--flag or -f)
36
+ text = re.sub(r'(--[\w-]+)', r'[cyan]\1[/cyan]', text)
37
+ text = re.sub(r'(\s-[a-zA-Z]\b)', r'[cyan]\1[/cyan]', text)
38
+
39
+ # Color file extensions
40
+ text = re.sub(r'(\.\w{2,4}\b)', r'[yellow]\1[/yellow]', text)
41
+
42
+ # Color example commands (batplot at start of line or after whitespace)
43
+ text = re.sub(r'(batplot\s+[^\n]+)', r'[green]\1[/green]', text)
44
+
45
+ # Color section headers (lines ending with :)
46
+ text = re.sub(r'^([A-Z][\w\s/()]+:)$', r'[bold blue]\1[/bold blue]', text, flags=re.MULTILINE)
47
+
48
+ # Color special markers
49
+ text = text.replace('•', '[bold]•[/bold]')
50
+
51
+ return text
52
+
53
+
54
+ def _print_help(text: str) -> None:
55
+ """Print help text with optional coloring.
56
+
57
+ Args:
58
+ text: Help text to print
59
+ """
60
+ if _HAS_RICH and _console:
61
+ colored_text = _colorize_help(text)
62
+ _console.print(colored_text)
63
+ else:
64
+ print(text)
7
65
 
8
66
 
9
67
  def _print_general_help() -> None:
@@ -46,11 +104,12 @@ def _print_general_help() -> None:
46
104
  " batplot -h xy # XY file plotting guide\n"
47
105
  " batplot -h ec # Electrochemistry (GC/dQdV/CV/CPC) guide\n"
48
106
  " batplot -h op # Operando guide\n\n"
49
- "Contact:\n"
107
+ "Contact & Updates:\n"
108
+ " Subscribe to batplot-lab@kjemi.uio.no for updates and feedback\n"
50
109
  " GitHub: https://github.com/tiandai-chem/batplot\n"
51
110
  " Email: tianda@uio.no\n"
52
111
  )
53
- print(msg)
112
+ _print_help(msg)
54
113
 
55
114
 
56
115
  def _print_xy_help() -> None:
@@ -87,7 +146,7 @@ def _print_xy_help() -> None:
87
146
  " --fullprof <args> : FullProf overlay options\n"
88
147
  " --stack : stack curves vertically (auto-enables normalization)\n"
89
148
  )
90
- print(msg)
149
+ _print_help(msg)
91
150
 
92
151
 
93
152
  def _print_ec_help() -> None:
@@ -123,7 +182,7 @@ def _print_ec_help() -> None:
123
182
  "rename axes, toggle ticks/titles/spines, print/export/import style (.bps/.bpsg), save session (.pkl).\n"
124
183
  "Note: Batch mode (--all) exports SVG files automatically; --interactive is for single-file plotting only.\n"
125
184
  )
126
- print(msg)
185
+ _print_help(msg)
127
186
 
128
187
 
129
188
  def _print_op_help() -> None:
@@ -141,7 +200,7 @@ def _print_op_help() -> None:
141
200
  "EC y-axis options (time ↔ ions), geometry tweaks, toggle spines/ticks/labels,\n"
142
201
  "print/export/import style, save session.\n"
143
202
  )
144
- print(msg)
203
+ _print_help(msg)
145
204
 
146
205
 
147
206
  def build_parser() -> argparse.ArgumentParser:
@@ -191,7 +250,10 @@ def parse_args(argv=None):
191
250
  _print_op_help()
192
251
  else:
193
252
  _print_general_help()
194
- print("\nUnknown help topic. Use: xy, ec, op")
253
+ if _HAS_RICH and _console:
254
+ _console.print("\n[yellow]Unknown help topic. Use: xy, ec, op[/yellow]")
255
+ else:
256
+ print("\nUnknown help topic. Use: xy, ec, op")
195
257
  sys.exit(0)
196
258
  # No help requested: parse fully
197
259
  return parser.parse_args(argv)
@@ -79,6 +79,22 @@ except ImportError:
79
79
  keep_canvas_fixed = False
80
80
 
81
81
 
82
+ def _natural_sort_key(filename: str) -> list:
83
+ """Generate a natural sorting key for filenames with numbers.
84
+
85
+ Converts 'file_10.xy' to ['file_', 10, '.xy'] so numerical parts are sorted numerically.
86
+ This ensures file_2.xy comes before file_10.xy (natural order).
87
+ """
88
+ parts = []
89
+ for match in re.finditer(r'(\d+|\D+)', filename):
90
+ text = match.group(0)
91
+ if text.isdigit():
92
+ parts.append(int(text))
93
+ else:
94
+ parts.append(text.lower())
95
+ return parts
96
+
97
+
82
98
  def batplot_main() -> int:
83
99
  """Main entry point for batplot CLI.
84
100
 
@@ -1131,7 +1147,7 @@ def batplot_main() -> int:
1131
1147
  all_xy_files = []
1132
1148
  unknown_ext_files = []
1133
1149
 
1134
- for f in sorted(os.listdir(os.getcwd())):
1150
+ for f in sorted(os.listdir(os.getcwd()), key=_natural_sort_key):
1135
1151
  if not os.path.isfile(os.path.join(os.getcwd(), f)):
1136
1152
  continue
1137
1153
  ext = os.path.splitext(f)[1].lower()
@@ -32,6 +32,21 @@ KNOWN_DIFFRACTION_EXT = {".xy", ".xye", ".qye", ".dat"}
32
32
  _two_theta_re = re.compile(r"2[tT]heta|2th", re.IGNORECASE)
33
33
  _q_re = re.compile(r"^q$", re.IGNORECASE)
34
34
 
35
+ def _natural_sort_key(path: Path) -> list:
36
+ """Generate a natural sorting key for filenames with numbers.
37
+
38
+ Converts 'file_10.xy' to ['file_', 10, '.xy'] so numerical parts are sorted numerically.
39
+ This ensures file_2.xy comes before file_10.xy (natural order).
40
+ """
41
+ parts = []
42
+ for match in re.finditer(r'(\d+|\D+)', path.name):
43
+ text = match.group(0)
44
+ if text.isdigit():
45
+ parts.append(int(text))
46
+ else:
47
+ parts.append(text.lower())
48
+ return parts
49
+
35
50
  def _infer_axis_mode(args, any_qye: bool, has_unknown_ext: bool):
36
51
  # Priority: explicit --xaxis, else .qye presence (Q), else wavelength (Q), else default 2theta with warning
37
52
  # If unknown extensions are present, use "user defined" mode
@@ -83,12 +98,12 @@ def plot_operando_folder(folder: str, args) -> Tuple[plt.Figure, plt.Axes, Dict[
83
98
  p = Path(folder)
84
99
  if not p.is_dir():
85
100
  raise FileNotFoundError(f"Not a directory: {folder}")
86
- # First try to find known diffraction files
87
- files = sorted([f for f in p.iterdir() if f.suffix.lower() in KNOWN_DIFFRACTION_EXT])
101
+ # First try to find known diffraction files (filter out macOS resource fork files starting with ._)
102
+ files = sorted([f for f in p.iterdir() if f.suffix.lower() in KNOWN_DIFFRACTION_EXT and not f.name.startswith("._")], key=_natural_sort_key)
88
103
  has_unknown_ext = False
89
104
  # If no known files found, accept any file extension (except .mpt which is for electrochemistry)
90
105
  if not files:
91
- files = sorted([f for f in p.iterdir() if f.is_file() and f.suffix.lower() != ".mpt"])
106
+ files = sorted([f for f in p.iterdir() if f.is_file() and f.suffix.lower() != ".mpt" and not f.name.startswith("._")], key=_natural_sort_key)
92
107
  has_unknown_ext = True
93
108
  if not files:
94
109
  raise FileNotFoundError("No data files found in folder (excluding .mpt files)")
@@ -137,7 +152,8 @@ def plot_operando_folder(folder: str, args) -> Tuple[plt.Figure, plt.Axes, Dict[
137
152
  Z = np.vstack(stack) # shape (n_scans, n_x)
138
153
 
139
154
  # Detect an electrochemistry .mpt file in the same folder (if any)
140
- mpt_files = sorted([f for f in p.iterdir() if f.suffix.lower() == ".mpt"]) # pick first if present
155
+ # Filter out macOS resource fork files (starting with ._)
156
+ mpt_files = sorted([f for f in p.iterdir() if f.suffix.lower() == ".mpt" and not f.name.startswith("._")], key=_natural_sort_key) # pick first if present
141
157
  has_ec = len(mpt_files) > 0
142
158
  ec_ax = None
143
159
 
@@ -2628,6 +2628,12 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax):
2628
2628
  current_mA = getattr(ec_ax, '_ec_current_mA', None)
2629
2629
  voltage_v = getattr(ec_ax, '_ec_voltage_v', None)
2630
2630
 
2631
+ if current_mA is None:
2632
+ print("Error: Current data is required for ion counting but is not available in the .mpt file.")
2633
+ print("The .mpt file must contain the '<I>/mA' column to use this feature.")
2634
+ print_menu()
2635
+ continue
2636
+
2631
2637
  if time_h is not None and current_mA is not None:
2632
2638
  t = np.asarray(time_h, float)
2633
2639
  i_mA = np.asarray(current_mA, float)
@@ -2983,9 +2989,13 @@ def operando_ec_interactive_menu(fig, ax, im, cbar, ec_ax):
2983
2989
  voltage_v = getattr(ec_ax, '_ec_voltage_v', None)
2984
2990
  current_mA = getattr(ec_ax, '_ec_current_mA', None)
2985
2991
  ln = getattr(ec_ax, '_ec_line', None)
2986
- if time_h is None or current_mA is None or ln is None:
2992
+ if time_h is None or ln is None:
2987
2993
  print("EC data not available for ion calculation.")
2988
2994
  print_menu(); continue
2995
+ if current_mA is None:
2996
+ print("Error: Current data is required for ion counting but is not available in the .mpt file.")
2997
+ print("The .mpt file must contain the '<I>/mA' column to use this feature.")
2998
+ print_menu(); continue
2989
2999
  sub = input("ey submenu: n=ions, t=time, q=back: ").strip().lower()
2990
3000
  if not sub or sub == 'q':
2991
3001
  print_menu(); continue
@@ -95,12 +95,50 @@ def read_mpt_file(fname: str, mode: str = 'gc', mass_mg: float = None):
95
95
  """
96
96
  import re
97
97
 
98
+ # Check if this is a full EC-Lab format or a simple 2-column export
99
+ is_eclab_format = False
100
+ with open(fname, 'r', encoding='utf-8', errors='ignore') as f:
101
+ first_line = f.readline().strip()
102
+ if first_line.startswith('EC-Lab ASCII FILE'):
103
+ is_eclab_format = True
104
+
105
+ # Handle simple 2-column time/voltage export format (for operando time mode)
106
+ if not is_eclab_format and mode == 'time':
107
+ try:
108
+ # Read with tab delimiter and handle European comma decimal separator
109
+ with open(fname, 'r', encoding='utf-8', errors='ignore') as f:
110
+ # Skip header line
111
+ f.readline()
112
+ time_vals = []
113
+ voltage_vals = []
114
+ for line in f:
115
+ line = line.strip()
116
+ if not line:
117
+ continue
118
+ parts = line.split('\t')
119
+ if len(parts) >= 2:
120
+ # Replace comma with period for European locale
121
+ time_vals.append(float(parts[0].replace(',', '.')))
122
+ voltage_vals.append(float(parts[1].replace(',', '.')))
123
+
124
+ if not time_vals:
125
+ raise ValueError("No data found in file")
126
+
127
+ time_s = np.array(time_vals)
128
+ voltage_v = np.array(voltage_vals)
129
+ current_mA = np.zeros_like(time_s) # No current data in simple format
130
+ return time_s, voltage_v, current_mA
131
+ except Exception as e:
132
+ raise ValueError(f"Failed to read simple .mpt format: {e}")
133
+
134
+ # For non-time modes or EC-Lab format, require full EC-Lab format
135
+ if not is_eclab_format:
136
+ raise ValueError(f"Not a valid EC-Lab .mpt file: {fname}")
137
+
98
138
  # Read header to find number of header lines
99
139
  header_lines = 0
100
140
  with open(fname, 'r', encoding='utf-8', errors='ignore') as f:
101
141
  first_line = f.readline().strip()
102
- if not first_line.startswith('EC-Lab ASCII FILE'):
103
- raise ValueError(f"Not a valid EC-Lab .mpt file: {fname}")
104
142
 
105
143
  # Find header lines count
106
144
  for line in f:
@@ -289,13 +327,16 @@ def read_mpt_file(fname: str, mode: str = 'gc', mass_mg: float = None):
289
327
  if voltage_col is None:
290
328
  available = ', '.join(f"'{c}'" for c in column_names)
291
329
  raise ValueError(f"Could not find 'Ewe/V' or 'Ewe' column.\nAvailable columns: {available}")
292
- if current_col is None:
293
- available = ', '.join(f"'{c}'" for c in column_names)
294
- raise ValueError(f"Could not find '<I>/mA' column.\nAvailable columns: {available}")
295
330
 
296
331
  time_data = data[:, time_col]
297
332
  voltage_data = data[:, voltage_col]
298
- current_data = data[:, current_col]
333
+
334
+ # Current column is optional (only needed for advanced features like ion counting)
335
+ if current_col is None:
336
+ # Return None for current_data if column not found
337
+ current_data = None
338
+ else:
339
+ current_data = data[:, current_col]
299
340
 
300
341
  return (time_data, voltage_data, current_data)
301
342
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: batplot
3
- Version: 1.3.5
3
+ Version: 1.3.7
4
4
  Summary: Interactive plotting for XRD, PDF, and XAS data (.xye, .xy, .qye, .dat, .csv, .gr, .nor, .chik, .chir)
5
5
  Author-email: Tian Dai <tianda@uio.no>
6
6
  License: MIT License
@@ -45,6 +45,7 @@ Description-Content-Type: text/markdown
45
45
  License-File: LICENSE
46
46
  Requires-Dist: numpy
47
47
  Requires-Dist: matplotlib
48
+ Requires-Dist: rich>=10.0.0
48
49
  Dynamic: license-file
49
50
 
50
51
  # batplot
@@ -179,3 +180,5 @@ Tian Dai (tianda@uio.no)
179
180
  University of Oslo
180
181
 
181
182
  **GitHub**: https://github.com/tiandai-chem/batplot
183
+
184
+ **Subscribe for Updates**: Join batplot-lab@kjemi.uio.no for updates, feature announcements, and community feedback
@@ -1,2 +1,3 @@
1
1
  numpy
2
2
  matplotlib
3
+ rich>=10.0.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "batplot"
7
- version = "1.3.5"
7
+ version = "1.3.7"
8
8
  description = "Interactive plotting for XRD, PDF, and XAS data (.xye, .xy, .qye, .dat, .csv, .gr, .nor, .chik, .chir)"
9
9
  authors = [
10
10
  { name = "Tian Dai", email = "tianda@uio.no" }
@@ -13,7 +13,8 @@ readme = "README.md"
13
13
  requires-python = ">=3.7"
14
14
  dependencies = [
15
15
  "numpy",
16
- "matplotlib"
16
+ "matplotlib",
17
+ "rich>=10.0.0"
17
18
  ]
18
19
  license = { file = "LICENSE" }
19
20
  classifiers = [
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