EMC Compliance Testing API Reference¶
Version: 0.1.0 Last Updated: 2026-01-08
Overview¶
TraceKit provides comprehensive EMC (Electromagnetic Compatibility) and EMI (Electromagnetic Interference) compliance testing capabilities for regulatory standards including FCC, CE/CISPR, MIL-STD, and automotive EMC requirements. The compliance module enables limit mask testing, violation detection, and automated report generation for emissions and immunity testing.
Quick Start¶
import tracekit as tk
from tracekit.compliance import (
load_limit_mask, check_compliance,
create_custom_mask, generate_compliance_report
)
# Load waveform and check against FCC limits
trace = tk.load("radiated_emissions.wfm")
mask = load_limit_mask("FCC_Part15_ClassB")
result = check_compliance(trace, mask)
# Generate compliance report
if result.passed:
print(f"✓ PASS - Margin: {result.margin_to_limit:.1f} dB")
else:
print(f"✗ FAIL - Violations: {result.violation_count}")
generate_compliance_report(result, "emc_report.html")
Core Functions¶
check_compliance()¶
Test signal against EMC limit mask for regulatory compliance.
from tracekit.compliance import check_compliance, load_limit_mask
# Load limit mask
mask = load_limit_mask("FCC_Part15_ClassB")
# Check compliance
result = check_compliance(
trace, # WaveformTrace or (freq, mag) tuple
mask, # LimitMask to test against
detector="peak", # "peak", "quasi-peak", "average", "rms"
frequency_range=None, # Optional (f_min, f_max) Hz
unit_conversion="V_to_dBuV" # "V_to_dBuV", "W_to_dBm", or None
)
# Analyze results
print(f"Status: {result.status}")
print(f"Margin: {result.margin_to_limit:.1f} dB")
print(f"Worst frequency: {result.worst_frequency / 1e6:.3f} MHz")
if not result.passed:
print(f"Violations: {result.violation_count}")
for v in result.violations[:5]:
print(f" {v}")
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
trace_or_spectrum |
WaveformTrace \|tuple[NDArray, NDArray] |
required | Trace to analyze or (freq, mag) spectrum |
mask |
LimitMask |
required | EMC limit mask |
detector |
DetectorType \|str |
"peak" |
Detector type for measurement |
frequency_range |
tuple[float, float] \|None |
None |
Frequency range to test (Hz) |
unit_conversion |
str \|None |
None |
Convert units ("V_to_dBuV", "W_to_dBm") |
Returns: ComplianceResult
ComplianceResult Attributes:
status: str- "PASS" or "FAIL"passed: bool- True if test passed (property)mask_name: str- Name of limit mask usedviolations: list[ComplianceViolation]- List of violationsviolation_count: int- Number of violations (property)margin_to_limit: float- Minimum margin in dB (negative = failing)worst_frequency: float- Frequency with worst margin (Hz)worst_margin: float- Worst margin value in dBspectrum_freq: NDArray[np.float64]- Tested frequency arrayspectrum_level: NDArray[np.float64]- Measured level arraylimit_level: NDArray[np.float64]- Limit level array (interpolated)detector: str- Detector type usedmetadata: dict[str, Any]- Additional result metadata
ComplianceViolation Attributes:
frequency: float- Violation frequency in Hzmeasured_level: float- Measured level in mask unitlimit_level: float- Limit level at this frequencyexcess_db: float- Amount exceeding limit (positive = violation)detector: str- Detector type usedseverity: str- Severity classification
DetectorType Values:
PEAK- Peak detector (highest instantaneous value)QUASI_PEAK- Quasi-peak detector (CISPR 16-1-1 weighted)AVERAGE- Average detector (Welch PSD-based)RMS- RMS detector
load_limit_mask()¶
Load a built-in or custom EMC limit mask by name.
from tracekit.compliance import load_limit_mask, AVAILABLE_MASKS
# Show available masks
print("Available masks:", AVAILABLE_MASKS)
# Load built-in mask
mask = load_limit_mask("FCC_Part15_ClassB")
print(f"Frequency range: {mask.frequency_range}")
print(f"Unit: {mask.unit}")
print(f"Distance: {mask.distance}m")
# Load custom mask from file
custom_mask = load_limit_mask("custom_limits.json")
# Load from custom directory
mask = load_limit_mask("MyMask", custom_path="./masks")
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Mask name or path to JSON file |
custom_path |
str \|Path \|None |
None |
Directory containing custom mask files |
Returns: LimitMask
Built-in Masks (AVAILABLE_MASKS):
FCC Standards:
FCC_Part15_ClassA- FCC Part 15 Class A (commercial) radiated emissionsFCC_Part15_ClassB- FCC Part 15 Class B (residential) radiated emissionsFCC_Part15_ClassB_Conducted- FCC Part 15 Class B conducted emissions
CE/CISPR Standards:
CE_CISPR22_ClassA- CISPR 22 Class A radiated emissionsCE_CISPR22_ClassB- CISPR 22 Class B radiated emissionsCE_CISPR32_ClassA- CISPR 32 Class A radiated emissionsCE_CISPR32_ClassB- CISPR 32 Class B radiated emissionsCE_CISPR32_ClassB_Conducted- CISPR 32 Class B conducted emissions
Military Standards:
MIL_STD_461G_CE102- MIL-STD-461G CE102 conducted emissionsMIL_STD_461G_RE102- MIL-STD-461G RE102 radiated emissionsMIL_STD_461G_CS101- MIL-STD-461G CS101 conducted susceptibility
Mask Aliases:
CISPR22,CISPR22_ClassB→CE_CISPR22_ClassBCISPR32,CISPR32_ClassB→CE_CISPR32_ClassB
create_custom_mask()¶
Create a custom EMC limit mask for specialized testing requirements.
from tracekit.compliance import create_custom_mask
# Create automotive EMC mask (CISPR 25-style limits)
mask = create_custom_mask(
name="CISPR_25_ClassB",
frequencies=[150e3, 30e6, 108e6, 1000e6], # Hz
limits=[74, 54, 44, 44], # dBuV
unit="dBuV",
description="CISPR 25 Class B radiated emissions",
detector="quasi-peak",
distance=1.0,
regulatory_body="ISO",
document="CISPR 25:2016"
)
# Create custom ESD immunity test mask
esd_mask = create_custom_mask(
name="IEC_61000_4_2_Level3",
frequencies=[1e3, 1e6, 100e6],
limits=[6, 6, 6], # kV
unit="kV",
description="IEC 61000-4-2 ESD immunity Level 3",
detector="peak"
)
# Mask with automatically sorted frequencies
mask = create_custom_mask(
name="Custom",
frequencies=[1000e6, 30e6, 100e6], # Will be auto-sorted
limits=[50, 60, 55]
)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Mask name |
frequencies |
list[float] \|NDArray |
required | Frequency points in Hz |
limits |
list[float] \|NDArray |
required | Limit values in specified unit |
unit |
str |
"dBuV" |
Limit unit ("dBuV", "dBm", "dBuV/m", etc.) |
description |
str |
"" |
Human-readable description |
**kwargs |
Additional LimitMask attributes | detector, distance, etc. |
Returns: LimitMask
Raises: ValueError if frequencies and limits have different lengths
Note: Frequencies are automatically sorted if not provided in ascending order.
generate_compliance_report()¶
Generate formatted EMC compliance test report in HTML, PDF, Markdown, or JSON.
from tracekit.compliance import generate_compliance_report, ComplianceReportFormat
# Generate HTML report with plot
report_path = generate_compliance_report(
result, # ComplianceResult from check_compliance()
"emc_report.html",
format="html", # "html", "pdf", "markdown", "json"
include_plot=True, # Include spectrum vs limit plot
title="Product EMC Test Report",
company_name="Acme Electronics",
dut_info={
"Model": "XYZ-100",
"Serial": "12345",
"Test Date": "2026-01-08",
"Lab": "EMC Test Lab A"
}
)
# Generate PDF report
generate_compliance_report(
result,
"emc_report.pdf",
format=ComplianceReportFormat.PDF,
include_plot=True,
title="FCC Part 15 Compliance Test"
)
# Generate JSON for automated processing
generate_compliance_report(
result,
"emc_report.json",
format="json"
)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
result |
ComplianceResult |
required | Compliance test result |
output_path |
str \|Path |
required | Output file path |
format |
ComplianceReportFormat \|str |
ComplianceReportFormat.HTML |
Report format |
include_plot |
bool |
True |
Include spectrum/limit plot (HTML/PDF) |
title |
str \|None |
"EMC Compliance Report" |
Report title |
company_name |
str \|None |
None |
Company name for header |
dut_info |
dict[str, str] \|None |
None |
Device Under Test information |
Returns: Path - Path to generated report
Report Formats:
HTML- Interactive HTML with embedded SVG plotPDF- PDF report (requires weasyprint)MARKDOWN- Markdown text reportJSON- Machine-readable JSON data
LimitMask Class¶
EMC limit mask definition with interpolation capabilities.
Attributes:
name: str- Standard name (e.g., "FCC_Part15_ClassB")description: str- Human-readable descriptionfrequency: NDArray[np.float64]- Frequency points in Hzlimit: NDArray[np.float64]- Limit values in specified unitunit: str- Limit unit ("dBuV", "dBm", "dBuV/m", etc.)standard: str- Standard designationdistance: float- Measurement distance in metersdetector: str- Required detector typeregulatory_body: str- Regulatory body (FCC, CE, MIL, ISO)document: str- Reference documentmetadata: dict[str, Any]- Additional metadata
Properties:
frequency_range: tuple[float, float]- (min, max) frequency range
Methods:
# Get limit at specific frequency
limit_at_100mhz = mask.get_limit_at_frequency(100e6)
# Interpolate to frequency array
freq_array = np.linspace(30e6, 1000e6, 1000)
limit_array = mask.interpolate(freq_array)
# Serialize to dictionary
mask_dict = mask.to_dict()
# Load from dictionary
mask = LimitMask.from_dict(mask_dict)
Practical Examples¶
Example 1: Testing Against CISPR 25 Automotive EMC Limits¶
import tracekit as tk
from tracekit.compliance import create_custom_mask, check_compliance, generate_compliance_report
import numpy as np
# Create CISPR 25 Class B radiated emissions mask (automotive)
cispr25_mask = create_custom_mask(
name="CISPR_25_ClassB_Radiated",
frequencies=[
150e3, 5.9e6, 6.2e6, 30e6, 54e6,
88e6, 108e6, 174e6, 230e6, 1000e6
],
limits=[
74, # 150 kHz - 5.9 MHz: 74 dBuV
74, # Peak at 5.9 MHz
60, # 6.2 MHz: 60 dBuV
54, # 30 MHz: 54 dBuV
50, # 54 MHz: 50 dBuV
48, # 88 MHz: 48 dBuV
44, # 108 MHz - 230 MHz: 44 dBuV
44,
44,
44 # Up to 1 GHz
],
unit="dBuV",
description="CISPR 25 Class B Radiated Emissions (Broadband)",
detector="quasi-peak",
distance=1.0, # 1 meter for automotive
regulatory_body="ISO",
document="CISPR 25:2016 Section 6.5"
)
# Load automotive component emissions data
trace = tk.load("automotive_ecu_emissions.wfm")
# Check compliance
result = check_compliance(
trace,
cispr25_mask,
detector="quasi-peak",
frequency_range=(150e3, 1000e6) # Test full automotive range
)
# Analyze results
print(f"CISPR 25 Compliance Test")
print(f"Status: {result.status}")
print(f"Margin to limit: {result.margin_to_limit:.1f} dB")
if not result.passed:
print(f"\nViolations detected: {result.violation_count}")
print("\nWorst violations:")
for v in sorted(result.violations, key=lambda x: x.excess_db, reverse=True)[:5]:
print(f" {v.frequency / 1e6:.3f} MHz: {v.excess_db:.1f} dB over limit")
else:
print(f"✓ PASS - Minimum margin: {result.margin_to_limit:.1f} dB")
print(f" at {result.worst_frequency / 1e6:.3f} MHz")
# Generate detailed report
generate_compliance_report(
result,
"cispr25_compliance_report.html",
title="CISPR 25 Automotive EMC Compliance Test",
company_name="Automotive Components Inc.",
dut_info={
"Device": "Engine Control Unit (ECU)",
"Part Number": "ECU-2000",
"Test Standard": "CISPR 25:2016",
"Test Date": "2026-01-08",
"Test Engineer": "J. Smith",
"Temperature": "25°C",
"Supply Voltage": "12V"
}
)
Example 2: FCC Part 15 Radiated Emissions Testing¶
import tracekit as tk
from tracekit.compliance import load_limit_mask, check_compliance
import matplotlib.pyplot as plt
# Load radiated emissions measurement
trace = tk.load("radiated_emissions_3m.wfm")
# Load FCC Part 15 Class B limit (residential equipment)
fcc_mask = load_limit_mask("FCC_Part15_ClassB")
print(f"FCC Part 15 Class B Radiated Emissions")
print(f"Frequency range: {fcc_mask.frequency_range[0] / 1e6:.1f} - "
f"{fcc_mask.frequency_range[1] / 1e6:.1f} MHz")
print(f"Measurement distance: {fcc_mask.distance}m")
print(f"Detector: {fcc_mask.detector}")
# Perform compliance test
result = check_compliance(
trace,
fcc_mask,
detector="quasi-peak",
unit_conversion="V_to_dBuV"
)
# Display results
print(f"\nTest Result: {result.status}")
print(f"Margin to limit: {result.margin_to_limit:.1f} dB")
print(f"Worst case: {result.worst_frequency / 1e6:.1f} MHz "
f"({result.worst_margin:.1f} dB margin)")
if result.passed:
print("\n✓ Device complies with FCC Part 15 Class B limits")
else:
print(f"\n✗ FAIL: {result.violation_count} violations detected")
print("\nViolation summary:")
for v in result.violations:
freq_mhz = v.frequency / 1e6
print(f" {freq_mhz:7.3f} MHz: {v.measured_level:5.1f} dBµV/m "
f"(limit: {v.limit_level:5.1f} dBµV/m, "
f"excess: +{v.excess_db:.1f} dB)")
# Plot spectrum vs limit
plt.figure(figsize=(12, 6))
plt.semilogx(result.spectrum_freq / 1e6, result.spectrum_level,
'b-', linewidth=1.5, label='Measured Spectrum')
plt.semilogx(result.spectrum_freq / 1e6, result.limit_level,
'r--', linewidth=2, label='FCC Limit')
# Highlight violations
if result.violations:
viol_freq = [v.frequency / 1e6 for v in result.violations]
viol_level = [v.measured_level for v in result.violations]
plt.scatter(viol_freq, viol_level, c='red', s=50,
marker='x', zorder=5, label='Violations')
plt.xlabel('Frequency (MHz)')
plt.ylabel('Field Strength (dBµV/m)')
plt.title('FCC Part 15 Class B Radiated Emissions @ 3m')
plt.grid(True, which='both', alpha=0.3)
plt.legend()
plt.tight_layout()
plt.savefig('fcc_part15_spectrum.png', dpi=150)
plt.show()
# Generate official compliance report
generate_compliance_report(
result,
"fcc_part15_compliance.html",
title="FCC Part 15 Class B Compliance Test",
dut_info={
"Product": "Wireless Router Model WR-5000",
"FCC ID": "ABC123456",
"Test Standard": "FCC Part 15 Subpart B",
"Test Method": "ANSI C63.4-2014",
"Test Distance": "3 meters",
"Antenna": "Biconical + Log Periodic",
"Test Lab": "EMC Lab XYZ (FCC Accredited)"
}
)
Example 3: IEC 61000-4-2 ESD Immunity Testing¶
import tracekit as tk
from tracekit.compliance import create_custom_mask, check_compliance
import numpy as np
# Create IEC 61000-4-2 ESD immunity mask (time-domain waveform envelope)
# ESD pulse characteristics per IEC 61000-4-2
esd_mask = create_custom_mask(
name="IEC_61000_4_2_Level3",
frequencies=[
0, # Start
1e-9, # 1ns rise time
30e-9, # First peak
60e-9, # Decay
100e-9 # End of interest
],
limits=[
0, # 0V start
6000, # 6kV peak (Level 3)
4000, # Decay envelope
1000, # Continued decay
0 # Return to baseline
],
unit="V",
description="IEC 61000-4-2 ESD Immunity Test Level 3 (Contact Discharge)",
detector="peak",
distance=0,
regulatory_body="IEC",
document="IEC 61000-4-2:2008"
)
# Load ESD pulse measurement
esd_pulse = tk.load("esd_discharge_contact.wfm")
# For time-domain testing, create (time, voltage) tuple
time = np.arange(len(esd_pulse.data)) / esd_pulse.metadata.sample_rate
voltage = np.abs(esd_pulse.data) # Use absolute value for envelope
# Check if pulse stays within envelope
result = check_compliance(
(time, voltage),
esd_mask,
detector="peak"
)
# Analyze immunity test results
print(f"IEC 61000-4-2 ESD Immunity Test - Level 3 (±6kV)")
print(f"Status: {result.status}")
if result.passed:
print(f"✓ PASS: Device immune to {6} kV ESD")
print(f" Margin: {result.margin_to_limit:.1f} dB")
else:
print(f"✗ FAIL: Device susceptible to ESD")
print(f" Peak voltage exceeded limit by: "
f"{-result.worst_margin:.1f} dB")
# Alternative: Test ESD spectrum for frequency content analysis
# This checks if ESD generator meets spectral requirements
esd_spectrum_mask = create_custom_mask(
name="IEC_61000_4_2_Spectrum",
frequencies=[10e6, 100e6, 500e6, 1000e6],
limits=[40, 35, 30, 25], # Approximate spectral content limits
unit="dBV",
description="IEC 61000-4-2 ESD Generator Spectrum Requirements"
)
# Compute spectrum and verify ESD generator
from tracekit.analyzers.spectral import fft
freq, mag = fft(esd_pulse)
spectrum_result = check_compliance(
(freq, np.abs(mag)),
esd_spectrum_mask,
detector="peak",
frequency_range=(10e6, 1000e6)
)
print(f"\nESD Generator Verification: {spectrum_result.status}")
if spectrum_result.passed:
print("✓ ESD generator meets spectral requirements")
Example 4: Creating Custom Compliance Masks¶
from tracekit.compliance import create_custom_mask, LimitMask
import numpy as np
import json
# Example 1: Multi-segment piecewise linear mask
# Typical for complex EMC requirements with frequency-dependent limits
mask = create_custom_mask(
name="Custom_MultiSegment",
frequencies=[
10e3, 150e3, # Below AM band: strict
150e3, 30e6, # AM band: very strict
30e6, 88e6, # Below FM: moderate
88e6, 108e6, # FM band: strict
108e6, 1e9 # Above FM: relaxed
],
limits=[
85, 70, # 10-150 kHz
70, 60, # 150 kHz - 30 MHz
60, 55, # 30-88 MHz
55, 50, # 88-108 MHz (FM)
50, 45 # 108 MHz - 1 GHz
],
unit="dBuV",
description="Custom Multi-Band EMC Limit",
detector="quasi-peak",
regulatory_body="Internal",
document="Internal Standard v2.0"
)
# Example 2: Logarithmic frequency sweep mask
# Common for wide bandwidth testing
frequencies = np.logspace(4, 9, 50) # 10 kHz to 1 GHz, 50 points
limits = 80 - 10 * np.log10(frequencies / 1e6) # Decreasing with frequency
log_mask = create_custom_mask(
name="Logarithmic_Sweep",
frequencies=frequencies,
limits=np.clip(limits, 30, 80), # Clip to reasonable range
unit="dBuV/m",
description="Logarithmic frequency sweep limit"
)
# Example 3: Notch limits (protect specific frequencies)
# Useful for protecting sensitive frequencies (GPS, WiFi, etc.)
base_limit = 50 # Base limit in dBuV
# Create frequency points with higher limits except at notches
freq_points = []
limit_points = []
# Main band: 1 MHz to 2 GHz
for f in np.logspace(6, 9, 100):
freq_points.append(f)
# GPS L1 notch at 1575.42 MHz (±10 MHz)
if 1565e6 <= f <= 1585e6:
limit_points.append(30) # Very strict at GPS
# WiFi 2.4 GHz notch (±50 MHz)
elif 2400e6 <= f <= 2500e6:
limit_points.append(35) # Strict at WiFi
else:
limit_points.append(base_limit) # Normal limit
notch_mask = create_custom_mask(
name="Protected_Bands",
frequencies=freq_points,
limits=limit_points,
unit="dBuV/m",
description="EMC limits with protected frequency bands (GPS, WiFi)",
detector="average"
)
# Example 4: Save custom mask to JSON for reuse
custom_mask_data = mask.to_dict()
with open("custom_emc_mask.json", "w") as f:
json.dump(custom_mask_data, f, indent=2)
# Load custom mask from JSON
with open("custom_emc_mask.json") as f:
loaded_data = json.load(f)
loaded_mask = LimitMask.from_dict(loaded_data)
print("Custom mask created and saved successfully")
print(f"Frequency range: {loaded_mask.frequency_range[0] / 1e6:.3f} - "
f"{loaded_mask.frequency_range[1] / 1e6:.3f} MHz")
Example 5: Generating Pass/Fail Reports¶
import tracekit as tk
from tracekit.compliance import load_limit_mask, check_compliance, generate_compliance_report
from pathlib import Path
# Test multiple devices and generate reports
devices = [
("Unit_001", "device_001_emissions.wfm"),
("Unit_002", "device_002_emissions.wfm"),
("Unit_003", "device_003_emissions.wfm"),
("Unit_004", "device_004_emissions.wfm"),
("Unit_005", "device_005_emissions.wfm"),
]
# Load compliance mask
mask = load_limit_mask("FCC_Part15_ClassB")
# Create reports directory
reports_dir = Path("emc_reports")
reports_dir.mkdir(exist_ok=True)
# Test each device
results_summary = []
for unit_id, filename in devices:
print(f"\nTesting {unit_id}...")
# Load trace
trace = tk.load(filename)
# Check compliance
result = check_compliance(
trace,
mask,
detector="quasi-peak",
unit_conversion="V_to_dBuV"
)
# Store summary
results_summary.append({
"unit_id": unit_id,
"status": result.status,
"margin_db": result.margin_to_limit,
"violations": result.violation_count,
"worst_freq_mhz": result.worst_frequency / 1e6
})
# Generate individual HTML report
generate_compliance_report(
result,
reports_dir / f"{unit_id}_emc_report.html",
title=f"FCC Part 15 Compliance Test - {unit_id}",
dut_info={
"Unit ID": unit_id,
"Test Standard": "FCC Part 15 Class B",
"Test Date": "2026-01-08"
}
)
# Generate JSON for data processing
generate_compliance_report(
result,
reports_dir / f"{unit_id}_emc_data.json",
format="json"
)
# Print result
status_symbol = "✓" if result.passed else "✗"
print(f" {status_symbol} {result.status} - Margin: {result.margin_to_limit:.1f} dB")
# Generate summary report
print("\n" + "="*60)
print("EMC COMPLIANCE TEST SUMMARY")
print("="*60)
print(f"{'Unit ID':<12} {'Status':<8} {'Margin (dB)':<12} {'Violations':<12} {'Worst Freq (MHz)'}")
print("-"*60)
pass_count = 0
for summary in results_summary:
status_symbol = "✓" if summary["status"] == "PASS" else "✗"
print(f"{summary['unit_id']:<12} {status_symbol} {summary['status']:<6} "
f"{summary['margin_db']:>10.1f} {summary['violations']:>10} "
f"{summary['worst_freq_mhz']:>16.3f}")
if summary["status"] == "PASS":
pass_count += 1
print("-"*60)
print(f"Pass Rate: {pass_count}/{len(devices)} ({pass_count/len(devices)*100:.1f}%)")
print(f"\nDetailed reports saved to: {reports_dir.absolute()}")
Advanced Usage¶
Using Pre-Computed Spectrum¶
For efficiency when testing multiple masks:
import tracekit as tk
from tracekit.analyzers.spectral import fft
from tracekit.compliance import load_limit_mask, check_compliance
import numpy as np
# Load trace once
trace = tk.load("emissions.wfm")
# Compute spectrum once
freq, mag = fft(trace)
spectrum = (freq, np.abs(mag))
# Test against multiple standards
masks = [
"FCC_Part15_ClassB",
"CE_CISPR32_ClassB",
"MIL_STD_461G_RE102"
]
for mask_name in masks:
mask = load_limit_mask(mask_name)
result = check_compliance(
spectrum, # Reuse spectrum
mask,
detector="peak",
unit_conversion="V_to_dBuV"
)
print(f"{mask_name}: {result.status} (margin: {result.margin_to_limit:.1f} dB)")
Custom Detector Implementation¶
For specialized detector types:
from tracekit.compliance import check_compliance, load_limit_mask
import numpy as np
# Implement custom detector processing
def custom_detector(trace):
"""Custom weighted averaging detector."""
from tracekit.analyzers.spectral import fft
freq, mag = fft(trace)
# Apply custom weighting (e.g., for specific standard)
weights = np.exp(-freq / 100e6) # Example: frequency-dependent weighting
weighted_mag = np.abs(mag) * weights
return freq, weighted_mag
# Use custom detector
trace = tk.load("emissions.wfm")
freq, mag = custom_detector(trace)
mask = load_limit_mask("FCC_Part15_ClassB")
result = check_compliance(
(freq, mag),
mask,
detector="peak" # Detector type for metadata
)
Margin Analysis and Trending¶
Track compliance margins over time:
import tracekit as tk
from tracekit.compliance import load_limit_mask, check_compliance
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
# Load historical test data
test_dates = [datetime.now() - timedelta(days=d) for d in range(30, 0, -5)]
test_files = [f"emissions_day{30-i}.wfm" for i in range(0, 30, 5)]
mask = load_limit_mask("FCC_Part15_ClassB")
margins = []
worst_freqs = []
for date, filename in zip(test_dates, test_files):
trace = tk.load(filename)
result = check_compliance(trace, mask)
margins.append(result.margin_to_limit)
worst_freqs.append(result.worst_frequency / 1e6)
# Plot margin trend
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# Margin over time
ax1.plot(test_dates, margins, 'o-', linewidth=2, markersize=8)
ax1.axhline(y=0, color='r', linestyle='--', label='Compliance Threshold')
ax1.axhline(y=6, color='orange', linestyle=':', label='Warning Level (6 dB)')
ax1.set_ylabel('Margin to Limit (dB)')
ax1.set_title('EMC Compliance Margin Trend')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Worst frequency over time
ax2.plot(test_dates, worst_freqs, 's-', linewidth=2, markersize=8, color='red')
ax2.set_xlabel('Date')
ax2.set_ylabel('Worst Case Frequency (MHz)')
ax2.set_title('Worst Case Frequency Trend')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('emc_margin_trend.png', dpi=150)
plt.show()
# Alert if margin is degrading
if margins[-1] < margins[0] - 3:
print("⚠ WARNING: EMC margin has degraded by >3 dB")
print(f" Initial: {margins[0]:.1f} dB")
print(f" Current: {margins[-1]:.1f} dB")
Best Practices¶
Detector Selection¶
Peak Detector:
- Use for initial screening and worst-case analysis
- Required by some military standards (MIL-STD-461)
- Fastest measurement, conservative results
Quasi-Peak Detector:
- Required for most CE/CISPR standards
- Accounts for repetition rate (per CISPR 16-1-1)
- More representative of human perception/interference
Average Detector:
- Required for some conducted emissions tests
- Better correlation with long-term interference
- Less sensitive to transients
RMS Detector:
- Used for power measurements
- Good for broadband noise analysis
# Example: Compare detector types
from tracekit.compliance import load_limit_mask, check_compliance
trace = tk.load("emissions.wfm")
mask = load_limit_mask("FCC_Part15_ClassB")
for detector in ["peak", "quasi-peak", "average", "rms"]:
result = check_compliance(trace, mask, detector=detector)
print(f"{detector:12s}: {result.status} (margin: {result.margin_to_limit:+.1f} dB)")
Frequency Range Selection¶
Test only relevant frequency ranges to improve performance:
# Automotive: 150 kHz - 2.5 GHz
result = check_compliance(trace, mask, frequency_range=(150e3, 2.5e9))
# Consumer electronics: 9 kHz - 1 GHz
result = check_compliance(trace, mask, frequency_range=(9e3, 1e9))
# Military: Often 10 kHz - 18 GHz
result = check_compliance(trace, mask, frequency_range=(10e3, 18e9))
Test Documentation¶
Always document test conditions:
dut_info = {
"Product": "Device Model",
"Serial Number": "SN12345",
"Firmware Version": "v2.3.1",
"Test Standard": "FCC Part 15B / ANSI C63.4",
"Test Date": "2026-01-08",
"Test Engineer": "Engineer Name",
"Test Lab": "Lab Name (Accreditation #)",
"Test Equipment": "Spectrum Analyzer, Antennas, etc.",
"Environmental Conditions": "23°C, 45% RH",
"Supply Voltage": "Nominal ±10%",
"Operating Mode": "Normal operation with load",
"Configuration": "All peripherals connected"
}
generate_compliance_report(result, "report.html", dut_info=dut_info)
Error Handling¶
from tracekit.compliance import load_limit_mask, check_compliance
from tracekit.core.exceptions import AnalysisError
try:
# Load mask
mask = load_limit_mask("FCC_Part15_ClassB")
# Check compliance
result = check_compliance(trace, mask)
except ValueError as e:
print(f"Invalid parameter: {e}")
except FileNotFoundError as e:
print(f"Mask file not found: {e}")
except AnalysisError as e:
print(f"Analysis failed: {e}")
# Validate spectrum data
if len(result.spectrum_freq) == 0:
print("Warning: No data in mask frequency range")
# Check for NaN/Inf
import numpy as np
if np.any(~np.isfinite(result.spectrum_level)):
print("Warning: Invalid values in spectrum")
See Also¶
- Analysis API - Spectral analysis and FFT
- Comparison & Limits API - Limit testing and masks
- Reporting API - Report generation
- Visualization API - Plotting spectra
References¶
EMC Standards¶
- FCC Part 15 - Code of Federal Regulations Title 47 Part 15
- CISPR 16-1-1 - Specification for radio disturbance and immunity measuring apparatus
- CISPR 22 - Information technology equipment - Radio disturbance characteristics
- CISPR 25 - Vehicles, boats and internal combustion engines - Radio disturbance characteristics
- CISPR 32 - Electromagnetic compatibility of multimedia equipment
- IEC 61000-4-2 - Electrostatic discharge immunity test
- IEC 61000-4-7 - General guide on harmonics and interharmonics measurements
- MIL-STD-461G - Requirements for the control of electromagnetic interference characteristics
- ANSI C63.4 - American National Standard for Methods of Measurement of Radio-Noise Emissions
- ANSI C63.2 - American National Standard for Electromagnetic Noise and Field Strength Instrumentation