Repository navigation
ZScope v3.0.0
Stable release for Windows. Includes all core EIS fitting features.
What's New in v3.0.0
ZScope 2.x.x was a very good tool for fitting one spectrum. This release is about everything around that: understanding what your circuit is doing, controlling how it is fitted, deciding which circuit is right, and — the reason for the major version — treating a set of spectra as the series it actually is.
A temperature sweep is no longer twelve unrelated files. Record what varied, fit the whole set in one pass, and read the activation energy off a plot ZScope makes for you. The numbers along the way carry their units, their uncertainties, and the sample geometry that turns ohms into a material property.
✨ New Features
Series analysis
Your spectra are a series, and ZScope now treats them as one A dataset used to be a spectrum and nothing else. It can now record what it was measured at — temperature, bias, elapsed time, concentration, current density, cycle number, partial pressure, pH — entered for the whole set in one grid rather than one dialog per file. Values can be read out of the file names, and if a name carries the number without a unit (cell_A_873_run1.txt), type the first one and ZScope learns where it sits and fills the rest. Units are converted for you: enter 600 °C and an Arrhenius plot receives 873.15 K.
Fit the whole series in one pass ⚡ Fit All Datasets (Ctrl+Shift+A) applies the circuit on the canvas to every spectrum, seeding each fit from the previous one's result. Ordered by your series variable, consecutive spectra differ by one step, so the last fit is genuinely close to the next — much faster and far steadier than a set of independent fits, and the reason the run is sequential rather than parallel. A spectrum that fails is marked and skipped rather than stopping the run, and a failed fit is never carried forward as a seed.
Series Analysis — the physics a single spectrum cannot give you 📈 F7. Plot any fitted parameter against whatever varied and fit a trend: Arrhenius for activation energy in eV and kJ/mol, Mott–Schottky for flat-band potential and dopant density, power law for a reaction order, exponential for a degradation rate, plus plain linear. Y can be a single parameter, the automatic sum of every resistance, or a combination you build by clicking — R2_R + R3_R for a partial sum, Rct_R * Cdl_C for a time constant reported in seconds — and combinations can be named and kept.
The window is careful about what it will and will not claim: datasets you exclude are listed separately from ones that could not supply a point, a series where every spectrum shares the same X value is refused rather than fitted, an uncertainty as large as its own value is reported as not determined instead of printed as a measurement, and a two-point fit says outright that its R² of 1 means nothing.
Getting the model right
Compare circuit models ⚖ Ctrl+M. Adding a component almost always lowers χ² — that is not evidence it is real. Every fit is now remembered as a candidate, ranked by AICc (or AIC, or BIC), with Δ and Akaike weights, so "two arcs or three?" gets an answer instead of a lower residual. Any candidate can be put back on the canvas. Crucially, models fitted to different data are refused rather than ranked: a truncated fit has a lower AIC simply because it has less to explain, and differencing them would name it the winner.
Separate DRT peaks that overlap 🔬 Peak detection finds maxima and integrates between the valleys either side. Two processes less than about a decade apart merge into one bump, and the valley split assigns an arbitrary share of the area to each — which, since the area is the resistance, means a wrong R that does not look wrong, because the total is still right. On two processes 0.3 decades apart, detection reports one of 330 Ω where there are really two, of 120 and 200 Ω.
Deconvolution fits the whole γ(τ) curve with a sum of R‖CPE peaks instead, separating them by shape, and recovers both exactly. Each fitted term is a circuit element, so the result drops straight into a circuit — with α fitted rather than inferred from a peak width.
And you can do it by hand, on the plot Deciding how many processes there are and roughly where is a judgement your eye makes better than any criterion — a criterion can only ever say "more terms fit better". So the peaks are draggable: sideways moves a time constant, up and down changes a resistance (a ZARC's apex height is a direct function of R, inverted exactly, so the handle height is the resistance). Click the curve to add a peak where the model falls short, remove one you do not believe, then Refit to optimise the numbers with your count and positions taken as given. The components are drawn dashed against the measured curve throughout, because a peak sitting where there is no shoulder looks obviously wrong on a plot and perfectly reasonable in a table.
The window is explicit about what not to over-read: R is the reliable output, α comes back biased low because the DRT's own regularisation broadens peaks, and the peak count is a judgement rather than a result.
Build a circuit from the DRT 🧩 Ctrl+Shift+D. The DRT already knows how many processes there are and where they sit; this turns that into a starting circuit instead of making you transcribe it. Each peak becomes an R‖CPE branch with R from the peak area, τ from its position, and α recovered from its width — a ZARC has an analytic DRT whose width depends only on α, so the measured width inverts to the exponent that produced it.
Control where the fit searches By default, fitting spreads its restarts across each parameter's whole allowed range, which means the result barely depends on what you entered. A new Search scope control changes that: Anchored draws restarts near your values, Local runs a single fit from them. Useful when you already know roughly where the answer is — and it is what makes a DRT-derived circuit and a sequential sweep worth doing.
Understanding one spectrum
See what a component does Double-click a part on the canvas and the plots show you where it acts: the stretch of the Nyquist curve it governs is redrawn thick and bright, the frequency band it dominates is shaded on Bode and Phase, and faint ghost curves show the spectrum with that component ten times larger and ten times smaller. The status bar puts it in words. Computed from your actual circuit rather than a textbook rule, so it stays correct for nested ladders, transmission lines and custom components. A component that turns out to have no measurable effect says so — a quick way to spot an element left dangling.
Change a parameter by dragging the plot The marker at the point of strongest influence can be dragged, and the component's value follows with the curve updating live. Because one parameter can only move that point along one line through the plane, ZScope uses the part of your drag that lies along it and ignores the rest. One undo step for the whole gesture.
Simulation Report (F6) The fit report stops at the edge of your data. This one covers the full simulation grid, updates live, and labels every row fitted, measured or extrapolated — so a number predicted beyond your measurement is never mistaken for one the fit was constrained by.
Reports that state what they mean
Normalize by your sample geometry An impedance in ohms describes your specimen, not your material. Enter the geometry once and the whole report converts: area-specific resistance, resistivity, conductivity, and for corrosion work the corrosion current density and penetration rate per ASTM G102. Eight applications: solid pellets, single cells and electrodes, membranes, liquid conductivity cells, corrosion and coatings, batteries and supercapacitors, and in-plane thin films.
The conversion follows each quantity's unit rather than its name, so resistances scale up, capacitances and CPE admittances scale down, and a CPE exponent or a relaxation time is correctly left alone. The symmetric-cell convention ASR = R·A/2 applies to the electrode resistance, not the electrolyte, so nothing is halved on its own: you tick which components are electrode processes and every halved parameter is marked (÷2). Geometry is stored per dataset, so a series of pellets each keeps its own.
Effective capacitance from a CPE Q is an admittance in S·sα and means nothing on its own. The report now converts it by both the Brug and Hsu–Mansfeld formulas, with τ and fpeak, and states which resistances each used — they can differ by tens of percent, and which applies is a modelling assumption that belongs to you. The manual used to instruct users to do this by hand.
Choose the imaginary sign, once Reports can show Z″ or −Z″, switched with one checkbox in both report windows. Headers always state which convention you are looking at, experimental and fitted always flip together, and phase follows. Exports record the convention too.
Every value states its unit, and derived values carry their uncertainty The fit report has a Unit column and labelled data columns; units travel into the TXT and JSON exports. Conductivity, icorr and effective capacitance now carry the uncertainty of the parameter they came from — a derived value with a blank error column beside a fitted one that has an error reads as the more certain of the two.
Smaller, but you will notice
DRT against τ or frequency, switchable instantly, with peak markers following. Scientific notation everywhere: type 1e-12, 1E-12, 1*10^-12 or a decimal comma; log-scaled parameters step ten clicks per decade. Each dataset keeps its own frequency range, saved with the project. Circuit notation explains its errors, showing the offending character and offering the legal rewritings rather than guessing.
🐛 Fixes
Small parameter values were being destroyed, not just displayed wrong Every parameter box was fixed at nine decimal places, so anything below a nanounit collapsed to zero: a fitted CPE admittance of 1.5 pS·sα read as 0.000000000 and the box genuinely held zero. Editing that row's bounds then wrote the zero back onto the component, replacing a fitted value with the range floor. Capacitances, CPE admittances, inductances and sub-nanosecond time constants were all affected. Separately, the minimum frequency box advertised a range down to 1 µHz but could not hold anything below 0.1 mHz.
Circuit notation could freeze the application Two independent problems, both of which hung rather than erroring. A / outside brackets sent the parser into an infinite loop — R/C alone was enough. And reading a circuit back from the canvas could recurse until the stack died on any circuit with several parallel pairs in series, because the path finder counted routes that doubled back through a node it had already left. A circuit that genuinely cannot be written as notation now says so.
A missing bracket silently built a different circuit R1-[R2/C1 was quietly read as [R2/C], dropping a character and producing a circuit that was then simulated and fitted without a word.
Report preferences were not being remembered Restoring saved settings applied widget values first and plain preferences last, so any widget missing from the build abandoned everything after it. Saving had the same shape: one unreadable widget discarded the entire settings file rather than just its own entry. Both now degrade one key at a time.
Import dialog: All / None left the row range stale The stale range was saved into the dataset's import settings, and nudging either box afterwards rebuilt the selection from it, silently discarding what you had just selected.
Panels that ignored the theme The geometry window and the plot-style panel were written in literal dark colours, so in Light and Nord they rendered as black rectangles. Both now follow the active theme — and the plot-style panel is rebuilt on each open, because it is created before your saved theme is restored and so was frozen in whatever was active at startup.
Batch fitting left each dataset half set up It stored the fit result but not the circuit that produced it, so switching to a batch-fitted dataset showed the previous dataset's circuit and simulated the wrong parameters. Each dataset now gets the full state a single fit would leave: its own circuit with its own fitted values, the node count needed to re-simulate it, and its own warm-start history.
The dataset panel stayed shut on a multi-dataset project It now opens itself the moment a session becomes multi-dataset — loading a project, or importing several files at once — while still respecting a collapse you made on purpose.
Batch fitting could only be run once per session Closing the window hid it rather than destroying it, and reopening raised a hidden window, which does nothing. It is now reusable, and refreshes onto whatever datasets are loaded at the time.
The JSON fit report contained no parameters It exported goodness-of-fit statistics and the full spectrum, but not the fitted parameters the fit exists to produce.
🔄 Also New: From v2.2.0
That release was about keeping your work. A whole session — every dataset, its circuit, its fits, its DRT results and its import settings — saves into a single .zscope project file and reopens exactly as you left it. Importing got faster too: batch-import a whole folder at once with columns detected automatically, paste data straight from Excel or Origin with Ctrl+V (including semicolon-separated European exports with decimal commas), and split a batch into one independently-reopenable project per dataset.
Earlier releases: v2.2.0 · v2.1.0 · v2.0.0
Have a feature request or found something that doesn't work as expected? Open an issue — it shapes what comes next.