batplot 1.8.31__tar.gz → 1.8.34__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.
- {batplot-1.8.31/batplot.egg-info → batplot-1.8.34}/PKG-INFO +2 -1
- {batplot-1.8.31 → batplot-1.8.34}/README.md +1 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/__init__.py +1 -1
- {batplot-1.8.31 → batplot-1.8.34}/batplot/args.py +10 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/batplot.py +189 -0
- batplot-1.8.34/batplot/canvas_interactive.py +1189 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/cpc_interactive.py +15 -14
- {batplot-1.8.31 → batplot-1.8.34}/batplot/data/CHANGELOG.md +16 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/electrochem_interactive.py +475 -230
- {batplot-1.8.31 → batplot-1.8.34}/batplot/interactive.py +9 -13
- {batplot-1.8.31 → batplot-1.8.34}/batplot/operando_ec_interactive.py +517 -578
- {batplot-1.8.31 → batplot-1.8.34}/batplot/session.py +796 -44
- batplot-1.8.34/batplot/showcol.py +468 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/style.py +5 -5
- {batplot-1.8.31 → batplot-1.8.34}/batplot/ui.py +7 -13
- {batplot-1.8.31 → batplot-1.8.34}/batplot/utils.py +32 -7
- {batplot-1.8.31 → batplot-1.8.34}/batplot/version_check.py +2 -2
- {batplot-1.8.31 → batplot-1.8.34/batplot.egg-info}/PKG-INFO +2 -1
- {batplot-1.8.31 → batplot-1.8.34}/batplot.egg-info/SOURCES.txt +2 -0
- {batplot-1.8.31 → batplot-1.8.34}/pyproject.toml +1 -1
- {batplot-1.8.31 → batplot-1.8.34}/LICENSE +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/MANIFEST.in +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/NOTICE +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/batch.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/cif.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/cli.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/color_utils.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/config.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/converters.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/data/USER_MANUAL.md +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/dev_upgrade.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/manual.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/modes.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/operando.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/plotting.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot/readers.py +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot.egg-info/dependency_links.txt +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot.egg-info/entry_points.txt +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot.egg-info/requires.txt +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/batplot.egg-info/top_level.txt +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/setup.cfg +0 -0
- {batplot-1.8.31 → batplot-1.8.34}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: batplot
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.34
|
|
4
4
|
Summary: Interactive plotting tool for material science (1D plot) and electrochemistry (GC, CV, dQ/dV, CPC, operando) with batch processing
|
|
5
5
|
Author-email: Tian Dai <tianda@uio.no>
|
|
6
6
|
License: MIT License
|
|
@@ -65,6 +65,7 @@ With a single line of command to easily plot publication-ready plots with custom
|
|
|
65
65
|
- **Session Persistence**: Save and reload complete plot states with `.pkl` files
|
|
66
66
|
- **Style Management**: Import/export plot styles as `.bps`/`.bpsg` files
|
|
67
67
|
- **Batch Processing**: Export each file separately with `--all`
|
|
68
|
+
- **Column preview**: `--showcol` prints numbered columns, header names when found, and the first 10 values per column (CSV, Excel, text, .mpt, .brml, Bruker .raw, etc.)
|
|
68
69
|
|
|
69
70
|
## Installation
|
|
70
71
|
|
|
@@ -14,6 +14,7 @@ With a single line of command to easily plot publication-ready plots with custom
|
|
|
14
14
|
- **Session Persistence**: Save and reload complete plot states with `.pkl` files
|
|
15
15
|
- **Style Management**: Import/export plot styles as `.bps`/`.bpsg` files
|
|
16
16
|
- **Batch Processing**: Export each file separately with `--all`
|
|
17
|
+
- **Column preview**: `--showcol` prints numbered columns, header names when found, and the first 10 values per column (CSV, Excel, text, .mpt, .brml, Bruker .raw, etc.)
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
@@ -168,6 +168,7 @@ def _print_general_help() -> None:
|
|
|
168
168
|
|
|
169
169
|
"More help:\n"
|
|
170
170
|
" batplot --version # Version and release info (with option to show full release notes)\n"
|
|
171
|
+
" batplot --showcol FILE [FILE...] # Preview column names + first 10 values per column\n"
|
|
171
172
|
" batplot --help # This help\n"
|
|
172
173
|
" batplot --help xy # XY file plotting guide\n"
|
|
173
174
|
" batplot --help ec # Electrochemistry (GC/dQdV/CV/CPC) guide\n"
|
|
@@ -231,6 +232,8 @@ def _print_xy_help() -> None:
|
|
|
231
232
|
" Works with --readcol for custom column layout (per-file, per-ext, or global):\n"
|
|
232
233
|
" batplot data.csv --readcol 3 4 --convert 1.54 q\n"
|
|
233
234
|
" batplot f1.txt --readcol 2 3 f2.txt --readcol 5 6 --convert 1.54 q\n"
|
|
235
|
+
" Directory: pass a folder to convert all .xy/.xye/.qye/.dat/.csv/.txt files:\n"
|
|
236
|
+
" batplot /path/to/folder --convert 0.25448 1.54\n"
|
|
234
237
|
" Examples:\n"
|
|
235
238
|
" batplot file.xye --convert 1.54 0.25\n"
|
|
236
239
|
" batplot file.xye --convert 1.54 q\n"
|
|
@@ -415,6 +418,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
415
418
|
help=argparse.SUPPRESS) # SUPPRESS hides from auto-generated help
|
|
416
419
|
parser.add_argument("--version", action="store_true", dest="version",
|
|
417
420
|
help="Show version and current release info, then exit.")
|
|
421
|
+
parser.add_argument(
|
|
422
|
+
"--showcol",
|
|
423
|
+
action="store_true",
|
|
424
|
+
help=argparse.SUPPRESS,
|
|
425
|
+
)
|
|
418
426
|
parser.add_argument("--manual", action="store_true", help=argparse.SUPPRESS)
|
|
419
427
|
|
|
420
428
|
# ====================================================================
|
|
@@ -491,6 +499,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
491
499
|
help=argparse.SUPPRESS)
|
|
492
500
|
parser.add_argument("--1d", action="store_true", dest="derivative_1d", help=argparse.SUPPRESS)
|
|
493
501
|
parser.add_argument("--2d", action="store_true", dest="derivative_2d", help=argparse.SUPPRESS)
|
|
502
|
+
parser.add_argument("--canvas", action="store_true", dest="canvas",
|
|
503
|
+
help="Canvas mode: combine multiple .pkl sessions into one layout. Use numbers to edit each panel.")
|
|
494
504
|
return parser
|
|
495
505
|
|
|
496
506
|
|
|
@@ -292,6 +292,143 @@ def _handle_cv_mode(args) -> int:
|
|
|
292
292
|
# Multiple files: create output directory
|
|
293
293
|
out_dir = ensure_subdirectory('Figures', os.getcwd())
|
|
294
294
|
|
|
295
|
+
# CV multi-file combined mode: one figure with all files overlaid (like GC/dQ/dV)
|
|
296
|
+
if len(data_files) > 1:
|
|
297
|
+
plt.rcParams.update({
|
|
298
|
+
'font.family': 'sans-serif',
|
|
299
|
+
'font.sans-serif': ['DejaVu Sans', 'Arial', 'Helvetica', 'STIXGeneral', 'Liberation Sans', 'Arial Unicode MS'],
|
|
300
|
+
'mathtext.fontset': 'dejavusans',
|
|
301
|
+
'font.size': 16
|
|
302
|
+
})
|
|
303
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
304
|
+
file_data = []
|
|
305
|
+
base_colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
|
|
306
|
+
'#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
|
|
307
|
+
for file_idx, ec_file in enumerate(data_files):
|
|
308
|
+
if not os.path.isfile(ec_file) or not (ec_file.lower().endswith('.mpt') or ec_file.lower().endswith('.txt')):
|
|
309
|
+
continue
|
|
310
|
+
try:
|
|
311
|
+
if ec_file.lower().endswith('.txt'):
|
|
312
|
+
voltage, current, cycles = read_biologic_txt_file(ec_file, mode='cv')
|
|
313
|
+
else:
|
|
314
|
+
mpt_result = read_mpt_file(ec_file, mode='cv')
|
|
315
|
+
voltage = mpt_result[0]
|
|
316
|
+
current = mpt_result[1]
|
|
317
|
+
cycles = mpt_result[2]
|
|
318
|
+
cyc_int_raw = np.array(np.rint(cycles), dtype=int)
|
|
319
|
+
if cyc_int_raw.size:
|
|
320
|
+
unique_cycles_raw = np.unique(cyc_int_raw)
|
|
321
|
+
valid_min_c = None
|
|
322
|
+
for c in sorted(unique_cycles_raw):
|
|
323
|
+
if np.sum(cyc_int_raw == c) >= 2:
|
|
324
|
+
valid_min_c = int(c)
|
|
325
|
+
break
|
|
326
|
+
if valid_min_c is not None:
|
|
327
|
+
shift = 1 - valid_min_c
|
|
328
|
+
else:
|
|
329
|
+
min_c = int(np.min(cyc_int_raw))
|
|
330
|
+
shift = 1 - min_c if min_c <= 0 else 0
|
|
331
|
+
else:
|
|
332
|
+
shift = 0
|
|
333
|
+
cyc_int = cyc_int_raw + shift
|
|
334
|
+
cycles_present = sorted(int(c) for c in np.unique(cyc_int)) if cyc_int.size else [1]
|
|
335
|
+
color = base_colors[file_idx % len(base_colors)]
|
|
336
|
+
cycle_lines = {}
|
|
337
|
+
file_lbl = os.path.basename(ec_file)
|
|
338
|
+
for cyc in cycles_present:
|
|
339
|
+
mask = (cyc_int == cyc)
|
|
340
|
+
idx = np.where(mask)[0]
|
|
341
|
+
if idx.size >= 2:
|
|
342
|
+
parts_x, parts_y = [], []
|
|
343
|
+
start = 0
|
|
344
|
+
for k in range(1, idx.size):
|
|
345
|
+
if idx[k] != idx[k - 1] + 1:
|
|
346
|
+
parts_x.append(voltage[idx[start:k]])
|
|
347
|
+
parts_y.append(current[idx[start:k]])
|
|
348
|
+
start = k
|
|
349
|
+
parts_x.append(voltage[idx[start:]])
|
|
350
|
+
parts_y.append(current[idx[start:]])
|
|
351
|
+
X, Y = [], []
|
|
352
|
+
for i, (px, py) in enumerate(zip(parts_x, parts_y)):
|
|
353
|
+
if i > 0:
|
|
354
|
+
X.append(np.array([np.nan]))
|
|
355
|
+
Y.append(np.array([np.nan]))
|
|
356
|
+
X.append(px)
|
|
357
|
+
Y.append(py)
|
|
358
|
+
x_b = np.concatenate(X) if X else np.array([])
|
|
359
|
+
y_b = np.concatenate(Y) if Y else np.array([])
|
|
360
|
+
label = f"{file_lbl}: {cyc}" if file_lbl else str(cyc)
|
|
361
|
+
if getattr(args, 'ro', False):
|
|
362
|
+
ln, = ax.plot(y_b, x_b, '-', color=color, linewidth=2.0, label=label, alpha=0.8)
|
|
363
|
+
else:
|
|
364
|
+
ln, = ax.plot(x_b, y_b, '-', color=color, linewidth=2.0, label=label, alpha=0.8)
|
|
365
|
+
cycle_lines[cyc] = ln
|
|
366
|
+
file_data.append({
|
|
367
|
+
'filename': os.path.basename(ec_file),
|
|
368
|
+
'display_name': os.path.basename(ec_file),
|
|
369
|
+
'cycle_lines': cycle_lines,
|
|
370
|
+
'visible': True,
|
|
371
|
+
'filepath': ec_file,
|
|
372
|
+
})
|
|
373
|
+
except Exception as _e:
|
|
374
|
+
print(f"CV multi-file: skip {ec_file}: {_e}")
|
|
375
|
+
if not file_data:
|
|
376
|
+
print("CV multi-file: no files loaded.")
|
|
377
|
+
return 1
|
|
378
|
+
if getattr(args, 'ro', False):
|
|
379
|
+
ax.set_xlabel('Current (mA)', labelpad=8.0)
|
|
380
|
+
ax.set_ylabel('Potential (V)', labelpad=8.0)
|
|
381
|
+
else:
|
|
382
|
+
ax.set_xlabel('Potential (V)', labelpad=8.0)
|
|
383
|
+
ax.set_ylabel('Current (mA)', labelpad=8.0)
|
|
384
|
+
ax.legend(title='Cycle')
|
|
385
|
+
fig._ec_legend_title = "Cycle"
|
|
386
|
+
fig.subplots_adjust(left=0.12, right=0.95, top=0.88, bottom=0.15)
|
|
387
|
+
if style_cfg:
|
|
388
|
+
try:
|
|
389
|
+
_apply_ec_style(fig, ax, style_cfg)
|
|
390
|
+
if hasattr(fig, 'canvas'):
|
|
391
|
+
fig.canvas.draw()
|
|
392
|
+
except Exception as e:
|
|
393
|
+
print(f"Warning: Error applying style file: {e}")
|
|
394
|
+
try:
|
|
395
|
+
plt.ion()
|
|
396
|
+
except Exception:
|
|
397
|
+
pass
|
|
398
|
+
plt.show(block=False)
|
|
399
|
+
try:
|
|
400
|
+
fig._ro_active = bool(getattr(args, "ro", False))
|
|
401
|
+
except Exception:
|
|
402
|
+
pass
|
|
403
|
+
try:
|
|
404
|
+
fig._bp_source_paths = [os.path.abspath(f.get('filepath', '')) for f in file_data if f.get('filepath')]
|
|
405
|
+
except Exception:
|
|
406
|
+
pass
|
|
407
|
+
if args.interactive:
|
|
408
|
+
try:
|
|
409
|
+
electrochem_interactive_menu(fig, ax, file_data=file_data)
|
|
410
|
+
except Exception as _ie:
|
|
411
|
+
print(f"Interactive menu failed: {_ie}")
|
|
412
|
+
plt.show()
|
|
413
|
+
else:
|
|
414
|
+
if out_dir and (args.savefig or getattr(args, 'out', None)):
|
|
415
|
+
out_path = getattr(args, 'out', None)
|
|
416
|
+
if not out_path:
|
|
417
|
+
ext = getattr(args, 'format', 'svg') or 'svg'
|
|
418
|
+
out_path = os.path.join(out_dir, f'CV_combined.{ext}')
|
|
419
|
+
try:
|
|
420
|
+
fig.savefig(out_path, dpi=300, bbox_inches='tight')
|
|
421
|
+
print(f"Saved {out_path}")
|
|
422
|
+
except Exception as e:
|
|
423
|
+
print(f"Could not save: {e}")
|
|
424
|
+
try:
|
|
425
|
+
plt.ioff()
|
|
426
|
+
except Exception:
|
|
427
|
+
pass
|
|
428
|
+
print(f"Processed {len(file_data)} CV files.")
|
|
429
|
+
plt.show(block=True)
|
|
430
|
+
return 0
|
|
431
|
+
|
|
295
432
|
for ec_file in data_files:
|
|
296
433
|
if not os.path.isfile(ec_file):
|
|
297
434
|
print(f"File not found: {ec_file}")
|
|
@@ -531,6 +668,15 @@ def batplot_main() -> int: # type: ignore
|
|
|
531
668
|
print(f"batplot v{__version__}")
|
|
532
669
|
return 0
|
|
533
670
|
|
|
671
|
+
# --showcol: print column names (if any) and first data rows, then exit
|
|
672
|
+
if getattr(args, "showcol", False):
|
|
673
|
+
if not args.files:
|
|
674
|
+
print("batplot --showcol: provide at least one data file.")
|
|
675
|
+
return 1
|
|
676
|
+
from .showcol import run_showcol
|
|
677
|
+
|
|
678
|
+
return run_showcol(args.files)
|
|
679
|
+
|
|
534
680
|
# ====================================================================
|
|
535
681
|
# STEP 2: VALIDATE INPUT
|
|
536
682
|
# ====================================================================
|
|
@@ -2457,6 +2603,33 @@ def batplot_main() -> int: # type: ignore
|
|
|
2457
2603
|
|
|
2458
2604
|
_maybe_expand_allfiles_argument(args, ec_mode_active)
|
|
2459
2605
|
|
|
2606
|
+
# ---------------- Handle --convert (before batch/sole logic) ----------------
|
|
2607
|
+
# When --convert is used with a directory, expand to all XY files in that directory
|
|
2608
|
+
if args.convert:
|
|
2609
|
+
if not args.files:
|
|
2610
|
+
print("Error: --convert requires file(s) or a directory to convert")
|
|
2611
|
+
exit(1)
|
|
2612
|
+
from .utils import natural_sort_key
|
|
2613
|
+
convert_ext = {'.xy', '.xye', '.qye', '.dat', '.csv', '.txt'}
|
|
2614
|
+
expanded = []
|
|
2615
|
+
for p in args.files:
|
|
2616
|
+
if os.path.isfile(p):
|
|
2617
|
+
expanded.append(p)
|
|
2618
|
+
elif os.path.isdir(p):
|
|
2619
|
+
for f in sorted(os.listdir(p), key=natural_sort_key):
|
|
2620
|
+
fp = os.path.join(p, f)
|
|
2621
|
+
if os.path.isfile(fp) and os.path.splitext(f)[1].lower() in convert_ext:
|
|
2622
|
+
expanded.append(fp)
|
|
2623
|
+
else:
|
|
2624
|
+
print(f"Warning: Not a file or directory: {p}")
|
|
2625
|
+
if expanded:
|
|
2626
|
+
from_param, to_param = args.convert
|
|
2627
|
+
convert_xrd_data(expanded, from_param, to_param, args=args)
|
|
2628
|
+
else:
|
|
2629
|
+
print("Error: No convertible files found (.xy, .xye, .qye, .dat, .csv, .txt)")
|
|
2630
|
+
exit(1)
|
|
2631
|
+
exit()
|
|
2632
|
+
|
|
2460
2633
|
if len(args.files) == 1:
|
|
2461
2634
|
sole = args.files[0]
|
|
2462
2635
|
if sole.lower() == 'all':
|
|
@@ -2474,6 +2647,22 @@ def batplot_main() -> int: # type: ignore
|
|
|
2474
2647
|
if not ec_mode_active and getattr(args, 'all', None) is not None:
|
|
2475
2648
|
batch_process(os.getcwd(), args)
|
|
2476
2649
|
exit()
|
|
2650
|
+
|
|
2651
|
+
# ---------------- Canvas mode: combine multiple .pkl sessions ----------------
|
|
2652
|
+
if getattr(args, 'canvas', False) and args.files:
|
|
2653
|
+
if all(f.lower().endswith('.pkl') for f in args.files):
|
|
2654
|
+
try:
|
|
2655
|
+
from .canvas_interactive import run_canvas_mode
|
|
2656
|
+
run_canvas_mode(args.files)
|
|
2657
|
+
except Exception as e:
|
|
2658
|
+
print(f"Canvas mode failed: {e}")
|
|
2659
|
+
import traceback
|
|
2660
|
+
traceback.print_exc()
|
|
2661
|
+
exit(1)
|
|
2662
|
+
exit()
|
|
2663
|
+
else:
|
|
2664
|
+
print("Canvas mode requires all files to be .pkl session files.")
|
|
2665
|
+
exit(1)
|
|
2477
2666
|
|
|
2478
2667
|
# ---------------- Normal (multi-file) path continues below ----------------
|
|
2479
2668
|
# Apply conditional default for delta (normal mode only)
|