############################################################
# Text Utilities
############################################################
from .visualID_Eng import fg, bg, hl
[docs]
def centerTitle(content=None):
'''
centers and renders as HTML a text in the notebook
font size = 16px, background color = dark grey, foreground color = white
'''
from IPython.display import display, HTML
display(HTML(f"<div style='text-align:center; font-weight: bold; font-size:16px;background-color: #343132;color: #ffffff'>{content}</div>"))
[docs]
def centertxt(content=None,font='sans', size=12,weight="normal",bgc="#000000",fgc="#ffffff"):
'''
centers and renders as HTML a text in the notebook
input:
- content = the text to render (default: None)
- font = font family (default: 'sans', values allowed = 'sans-serif' | 'serif' | 'monospace' | 'cursive' | 'fantasy' | ...)
- size = font size (default: 12)
- weight = font weight (default: 'normal', values allowed = 'normal' | 'bold' | 'bolder' | 'lighter' | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 )
- bgc = background color (name or hex code, default = '#ffffff')
- fgc = foreground color (name or hex code, default = '#000000')
'''
from IPython.display import display, HTML
display(HTML(f"<div style='text-align:center; font-family: {font}; font-weight: {weight}; font-size:{size}px;background-color: {bgc};color: {fgc}'>{content}</div>"))
[docs]
def smart_trim(img):
"""
Determines the bounding box of the meaningful content in an image.
This function automatically detects if the image has transparency (Alpha channel).
If it does, it calculates the bounding box based on non-transparent pixels.
If the image is opaque, it assumes a white background and calculates the
bounding box by detecting differences from a pure white canvas.
Args:
img (PIL.Image.Image): The source image object.
Returns:
tuple: A 4-tuple (left, upper, right, lower) defining the crop box,
or None if the image is uniform/empty.
"""
import sys
from pathlib import Path
from PIL import Image, ImageOps
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
return img.getbbox()
else:
bg = Image.new("RGB", img.size, (255, 255, 255))
diff = ImageOps.difference(img.convert("RGB"), bg)
return diff.getbbox()
[docs]
def crop_images(input_files, process_folder=False):
"""
Trims whitespace or transparency from image files and saves the results.
If process_folder is True, input_files is treated as a directory path,
and all images within (excluding those ending in -C) are processed.
Otherwise, input_files is treated as a single file path or a list of paths.
The function preserves original image metadata (DPI, ICC profiles).
Args:
input_files (str, Path, or list): File path(s) or a directory path.
process_folder (bool): If True, treats input_files as a directory to crawl.
Returns:
None: Prints status messages to the console for each file processed.
"""
import sys
from pathlib import Path
from PIL import Image, ImageOps
files_to_process = []
if process_folder:
folder_path = Path(input_files)
if folder_path.is_dir():
# On récupère png, jpg, jpeg (insensible à la casse)
# On exclut les fichiers finissant déjà par -C
extensions = ('*.png', '*.jpg', '*.jpeg', '*.PNG', '*.JPG', '*.JPEG')
for ext in extensions:
for f in folder_path.glob(ext):
if not f.stem.endswith('-C'):
files_to_process.append(f)
else:
print(f"❌ Error: {input_files} is not a valid directory.")
return
else:
# Logique existante pour fichier unique ou liste
if isinstance(input_files, (str, Path)):
files_to_process = [Path(input_files)]
else:
files_to_process = [Path(f) for f in input_files]
for path in files_to_process:
if not path.exists() or path.is_dir():
continue
output_path = path.with_name(f"{path.stem}-C{path.suffix}")
try:
with Image.open(path) as img:
info = img.info.copy()
bbox = smart_trim(img)
if bbox:
img_trimmed = img.crop(bbox)
img_trimmed.save(output_path, **info)
print(f"✅ Saved: {output_path.name}")
else:
print(f"⚠️ Skipping {path.name}: No content detected.")
except Exception as e:
print(f"❌ Error processing {path.name}: {e}")
############################################################
# Export Utilities
############################################################
[docs]
def save_fig(path_with_name: str, fig=None, dpi: int = 300, **kwargs):
"""
Saves a figure to a path, automatically creating missing directories.
Works with the current plt or a specific figure object.
Args:
path_with_name (str): The path and filename (e.g., "results/plots/energy.svg").
fig (matplotlib.figure.Figure, optional): A specific figure object.
If None, uses plt.gcf().
dpi (int): Resolution for raster formats. Defaults to 300.
**kwargs: Additional arguments passed to savefig (e.g., transparent=True).
"""
import matplotlib.pyplot as plt
from pathlib import Path
# 1. Parse path and filename
full_path = Path(path_with_name)
# 2. Automatically create the directory structure
if full_path.parent:
full_path.parent.mkdir(parents=True, exist_ok=True)
# 3. Determine if we use a specific fig object or the global plt
# If no fig is provided, we use the 'Current Figure' (gcf)
target = fig if fig is not None else plt.gcf()
# 4. Save the figure
target.savefig(full_path, dpi=dpi, bbox_inches='tight', **kwargs)
print(f"✅ Figure saved to: {full_path}")
[docs]
def save_data(df, path_with_name: str, **kwargs):
"""
Saves a Pandas DataFrame to CSV or Excel, automatically creating missing directories.
Args:
df (pd.DataFrame): The DataFrame to save.
path_with_name (str): The path and filename (e.g., "data/results.csv").
**kwargs: Additional arguments passed to to_csv or to_excel (e.g., index=True).
"""
from pathlib import Path
full_path = Path(path_with_name)
if full_path.parent:
full_path.parent.mkdir(parents=True, exist_ok=True)
ext = full_path.suffix.lower()
# Default index to False unless specified by the user
index_option = kwargs.pop('index', False)
if ext == '.csv':
df.to_csv(full_path, index=index_option, **kwargs)
elif ext in ['.xlsx', '.xls']:
df.to_excel(full_path, index=index_option, **kwargs)
else:
raise ValueError(f"Unsupported file extension: {ext}. Use .csv, .xlsx, or .xls")
print(f"✅ Data saved to: {full_path}")
# --------------------------------------------------------------
import shutil
import sys
import argparse
from pathlib import Path
import pyphyschemtools
[docs]
def get_qc_examples(suite=None):
"""
Copies QC examples archive to the current directory.
Supports terminal CLI (with help) and direct Python calls.
"""
# 1. Handle CLI Arguments if called from terminal
if suite is None:
parser = argparse.ArgumentParser(
description="Copy Quantum Chemistry example archives to the current directory."
)
parser.add_argument(
"suite",
help="Specify the suite to retrieve: 'VASP' or 'G16' (case-insensitive)",
type=str
)
# If no arguments are passed, show help and exit
if len(sys.argv) == 1:
parser.print_help()
return
args = parser.parse_args()
suite = args.suite
# 2. Case-insensitivity logic
suite_upper = suite.upper()
if suite_upper not in ["VASP", "G16"]:
print(f"❌ Error: '{suite}' is not a valid suite. Please choose 'VASP' or 'G16'.")
return
# 3. Path Logic
pkg_root = Path(pyphyschemtools.__file__).parent
archive_name = f"tools4{suite_upper}-Examples.tar.bz2"
source = pkg_root / "data_examples" / "QCCorner" / archive_name
if not source.exists():
print(f"❌ Error: Archive {archive_name} not found in the package installation.")
return
destination = Path.cwd() / archive_name
try:
shutil.copy(source, destination)
print(f"✅ Success! {suite_upper} examples archive copied to: {destination}")
print(f"To extract, run: tar -xvjf {archive_name}")
except Exception as e:
print(f"❌ Failed to copy archive: {e}")