Spaces:
Configuration error
Configuration error
| 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 |