qurashiubaid's picture
Upload 7 files
638afcf verified
import numpy as np
import pandas as pd
from scipy.signal import find_peaks
from scipy.ndimage import gaussian_filter1d
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
class LightweightAnalyzer:
"""Lightweight analyzer that works on Hugging Face Spaces"""
def __init__(self):
# Predefined reference patterns (no internet needed)
self.reference_phases = {
'Fe3O4': {'peaks': [30.1, 35.5, 43.1, 53.4, 57.0, 62.6]},
'CoFe2O4': {'peaks': [30.2, 35.6, 43.2, 53.5, 57.1, 62.7]},
'TiO2_anatase': {'peaks': [25.3, 37.8, 48.0, 53.9, 55.1, 62.7]},
'TiO2_rutile': {'peaks': [27.4, 36.1, 41.2, 54.3, 56.6, 69.0]}
}
def load_csv(self, file_path):
"""Load CSV with auto column detection"""
df = pd.read_csv(file_path)
cols = [c.lower() for c in df.columns]
# X-axis
if 'wavelength' in cols:
x_col = df.columns[cols.index('wavelength')]
elif '2theta' in cols:
x_col = df.columns[cols.index('2theta')]
elif 'h' in cols:
x_col = df.columns[cols.index('h')]
else:
x_col = df.columns[0]
# Y-axis
if 'intensity' in cols:
y_col = df.columns[cols.index('intensity')]
elif 'm' in cols:
y_col = df.columns[cols.index('m')]
elif 'absorption' in cols:
y_col = df.columns[cols.index('absorption')]
else:
y_col = df.columns[1]
x = df[x_col].values.astype(float)
y = df[y_col].values.astype(float)
valid = np.isfinite(x) & np.isfinite(y)
return x[valid], y[valid]
def analyze_xrd(self, x, y):
"""Lightweight XRD analysis"""
# Find peaks
peaks, _ = find_peaks(y, height=np.max(y)*0.1, distance=10)
peak_positions = x[peaks].tolist()
# Phase matching (simple nearest neighbor)
best_match = "Unknown"
best_score = 0
for phase, ref in self.reference_phases.items():
score = 0
for ref_peak in ref['peaks']:
if any(abs(ref_peak - p) < 2.0 for p in peak_positions):
score += 1
if score > best_score:
best_score = score
best_match = phase
# Estimate crystallite size (simplified Scherrer)
if len(peaks) > 0:
# Estimate FWHM of strongest peak
main_peak = peaks[np.argmax(y[peaks])]
half_max = y[main_peak] / 2
left = main_peak
while left > 0 and y[left] > half_max:
left -= 1
right = main_peak
while right < len(y)-1 and y[right] > half_max:
right += 1
fwhm = x[right] - x[left] if right > left else 1.0
theta = x[main_peak] / 2
size = 0.9 * 1.54 / (fwhm * np.cos(np.radians(theta)) * np.pi/180)
else:
size = 0
return {
'peaks': peak_positions,
'phase': best_match,
'crystallite_size_nm': float(size),
'amorphous_ratio': float(np.mean(gaussian_filter1d(y, sigma=50)) / np.mean(y))
}
def analyze_vsm(self, x, y):
"""Lightweight VSM analysis"""
# Normalize
y = y / np.max(np.abs(y))
# Coercivity
mid = len(x) // 2
asc_y = y[mid:]
asc_x = x[mid:]
zero_cross = np.where(np.diff(np.sign(asc_y)))[0]
Hc = float(asc_x[zero_cross[0]]) if len(zero_cross) > 0 else 0.0
# Remanence
zero_idx = np.argmin(np.abs(x))
Mr = float(y[zero_idx])
return {'Hc': Hc, 'Mr': Mr}
def analyze_uvvis(self, x, y):
"""Lightweight UV-Vis analysis"""
# Normalize
y = y / np.max(y)
# Find absorption edge (80% of max)
edge_idx = np.argmax(y > 0.8 * np.max(y))
if edge_idx == 0:
edge_wl = x[-1]
else:
edge_wl = x[edge_idx]
# Estimate bandgap
energy = 1240 / edge_wl
return {'bandgap_eV': float(energy), 'edge_wavelength_nm': float(edge_wl)}
def analyze_pl(self, x, y):
"""Lightweight PL analysis"""
# Normalize
y = y / np.max(y)
# Find main peak
peaks, _ = find_peaks(y, height=np.max(y)*0.1, distance=10)
if len(peaks) > 0:
main_peak = peaks[np.argmax(y[peaks])]
peak_wl = float(x[main_peak])
# Estimate FWHM
half_max = y[main_peak] / 2
left = main_peak
while left > 0 and y[left] > half_max:
left -= 1
right = main_peak
while right < len(y)-1 and y[right] > half_max:
right += 1
fwhm = float(x[right] - x[left]) if right > left else 0.0
else:
peak_wl = 0.0
fwhm = 0.0
return {'peak_wavelength_nm': peak_wl, 'fwhm_nm': fwhm}
def generate_report(self, results):
"""Generate analysis report"""
lines = []
lines.append("=" * 50)
lines.append("🔬 MULTI-MODAL MATERIALS ANALYSIS")
lines.append("=" * 50)
if 'xrd' in results:
xrd = results['xrd']
lines.append(f"\n📊 XRD RESULTS:")
lines.append(f" • Identified phase: {xrd['phase']}")
lines.append(f" • Crystallite size: {xrd['crystallite_size_nm']:.1f} nm")
lines.append(f" • Amorphous ratio: {xrd['amorphous_ratio']:.3f}")
if 'vsm' in results:
vsm = results['vsm']
lines.append(f"\n🧲 VSM RESULTS:")
lines.append(f" • Coercivity (Hc): {vsm['Hc']:.1f} Oe")
lines.append(f" • Remanence (Mr): {vsm['Mr']:.3f}")
if 'uvvis' in results:
uvvis = results['uvvis']
lines.append(f"\n🌈 UV-VIS RESULTS:")
lines.append(f" • Bandgap: {uvvis['bandgap_eV']:.2f} eV")
lines.append(f" • Absorption edge: {uvvis['edge_wavelength_nm']:.1f} nm")
if 'pl' in results:
pl = results['pl']
lines.append(f"\n💡 PL RESULTS:")
lines.append(f" • Emission peak: {pl['peak_wavelength_nm']:.1f} nm")
lines.append(f" • FWHM: {pl['fwhm_nm']:.1f} nm")
lines.append("\n💡 NOTE: This is a lightweight analysis.")
lines.append("For advanced analysis, use local installation.")
lines.append("=" * 50)
return "\n".join(lines)
def generate_plots(self, results, sample_name, output_dir="."):
"""Generate plots"""
import os
os.makedirs(output_dir, exist_ok=True)
plots = []
if 'xrd' in results:
plt.figure(figsize=(6, 4))
# We don't have raw data, so skip plotting
plt.text(0.5, 0.5, "XRD: Phase identified", ha='center', va='center')
plt.title(f"XRD - {sample_name}")
path = os.path.join(output_dir, f"{sample_name}_xrd.png")
plt.savefig(path, dpi=150, bbox_inches='tight')
plt.close()
plots.append(path)
# Similar for other modalities (simplified)
for modality in ['vsm', 'uvvis', 'pl']:
if modality in results:
plt.figure(figsize=(6, 4))
plt.text(0.5, 0.5, f"{modality.upper()}: Analyzed", ha='center', va='center')
plt.title(f"{modality.upper()} - {sample_name}")
path = os.path.join(output_dir, f"{sample_name}_{modality}.png")
plt.savefig(path, dpi=150, bbox_inches='tight')
plt.close()
plots.append(path)
return plots