quantized-lab 0.8.1__tar.gz → 0.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/PKG-INFO +1 -1
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/pyproject.toml +1 -1
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/__init__.py +1 -1
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure.py +17 -5
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_break.py +10 -4
- quantized_lab-0.10.0/src/quantized/calc/figure_labels.py +159 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_overrides.py +25 -1
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_page.py +4 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_scale.py +4 -1
- quantized_lab-0.10.0/src/quantized/calc/figure_shapes.py +153 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_ticks.py +31 -2
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_bumps.py +30 -4
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_scan.py +2 -7
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fitting.py +17 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/curve_style_color.py +13 -8
- quantized_lab-0.10.0/src/quantized/io/origin_project/fidelity.py +192 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_layers.py +2 -1
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figures.py +13 -3
- quantized_lab-0.10.0/src/quantized/io/origin_project/graph_preview.py +156 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj_curves.py +87 -22
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/templates.py +5 -3
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export_figures.py +6 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export_page.py +2 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/fitting.py +20 -3
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/parsers.py +31 -6
- quantized_lab-0.8.1/src/quantized/web/assets/index-BiZzN7J6.css → quantized_lab-0.10.0/src/quantized/web/assets/index-DkU851uO.css +1 -1
- quantized_lab-0.10.0/src/quantized/web/assets/index-N-btoovo.js +27 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/index.html +2 -2
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/tools/origin_compare/README.md +1 -1
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/tools/visual/README.md +44 -5
- quantized_lab-0.8.1/src/quantized/calc/figure_labels.py +0 -62
- quantized_lab-0.8.1/src/quantized/web/assets/index-BHmmCL-x.js +0 -27
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/.gitignore +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/LICENSE +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/NOTICE +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/README.md +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/__main__.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/api.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/app.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/__init__.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/_clipfit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/_natural_neighbor.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/aggregate.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/backgrounds.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/baseline.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/batch_fit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/constants.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/corrections.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/crystallography.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/diffusion.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/electrical.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/electrochemistry.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/element_data.json +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/element_data.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/errors.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_categorical.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_corner.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_facets.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_field.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_hitmap.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_map.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_statplots.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_styles.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/figure_ternary.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_autoguess.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_bootstrap.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_constraints.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_equation.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_findxy.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_models.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_models_special.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_odr.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/fit_stats.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/formula.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/global_curve_fit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/global_fit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/interp2d.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/linecut.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/magnetic.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/magnetometry.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/map.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/mcmc.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/optics.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/pawley.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_batch.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_fit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_integrate.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_multifit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peak_track.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peaks.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/peakshapes.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/plotting.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/processing.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/qspace.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/reductions.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/reductions_fft.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/refl_sld_presets.json +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/reflectivity.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/registry.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/relaxation.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/report.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/report_emit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/resample.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/rsm.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/rsm_analyze.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/semiconductor.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/sld.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/sld_formula.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/spectral.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/statplots.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_anova2.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_anova_ext.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_dist.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_glm.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_multivar.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_roc.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_survival.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/stats_tests.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/substrates.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/superconductor.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/surface_fit.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/surface_models.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/thermal.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/thin_film.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/unit_convert.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/units.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/vacuum.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/calc/xray.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/cli.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/datastruct.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/__init__.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/_hdf5_layout.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/_jcamp_asdf.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/_xrdml_scan.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/base.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/bruker_brml.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/bruker_raw.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/cif.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/consolidated.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/delimited.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/excel.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/hdf5.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/import_filters.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/import_preview.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/jcamp.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/lakeshore.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/ncnr.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/netcdf.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/opus.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_com.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/__init__.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/annotation_marks.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/container.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_geometry.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figure_text.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/figures_opju.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/notes.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opj_shapes.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_axis_real_form.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_axis_specimen_form.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_codec.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_curves.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_curves_allcols.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_figure_curves.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_figure_text.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/opju_reports.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/origin_richtext.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/preview.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/tree.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/tree_opju.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/windows.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/windows_opju.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/writer.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/origin_project/writer_blocks.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/qd.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/refl1d.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/registry.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/report_export.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/rigaku.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/sims.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/spc.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/xrd_csv.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/io/xrdml.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/jobs.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/__init__.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/contract.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/loader.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/plugins/steps.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/__init__.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_bookcache.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_export_common.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_payload.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/_uploadcache.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/aggregate.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/baseline.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/books.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/calc.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/corrections.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/crystallography.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/diffusion.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/electrical.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/electrochemistry.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/export_facets.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/fitting_bumps.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/import_template.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/import_wizard.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/jobs_api.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/magnetic.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/magnetometry.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/optics.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/peaks.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/plot.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/reductions.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/reference.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/reflectivity.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/report_export.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/rsm.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/samples.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/semiconductor.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/sld.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/spectral.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/statplots.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/stats.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/stats_design.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/substrates.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/superconductor.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/thermal.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/thin_film.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/vacuum.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/routes/xray.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/samples/demo_vsm.csv +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/server_launch.py +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
- {quantized_lab-0.8.1 → quantized_lab-0.10.0}/src/quantized/web/loading.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quantized-lab
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: Plot, analyze, and publish lab data - magnetometry, XRD, reflectometry - with OriginPro file interop, golden-verified analysis, and reproducible pipelines.
|
|
5
5
|
Project-URL: Homepage, https://github.com/pquarterman17/quantized
|
|
6
6
|
Project-URL: Documentation, https://github.com/pquarterman17/quantized/wiki
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quantized-lab"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.0"
|
|
4
4
|
description = "Plot, analyze, and publish lab data - magnetometry, XRD, reflectometry - with OriginPro file interop, golden-verified analysis, and reproducible pipelines."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -27,7 +27,7 @@ from quantized.calc.figure_labels import safe_mathtext_label # noqa: E402
|
|
|
27
27
|
from quantized.calc.figure_overrides import _apply_overrides, _validate_overrides # noqa: E402
|
|
28
28
|
from quantized.calc.figure_scale import apply_axis_scale, resolve_axis_scale # noqa: E402
|
|
29
29
|
from quantized.calc.figure_styles import FigureStyle, figure_style # noqa: E402
|
|
30
|
-
from quantized.calc.figure_ticks import apply_tick_formats # noqa: E402
|
|
30
|
+
from quantized.calc.figure_ticks import apply_tick_formats, apply_tick_steps # noqa: E402
|
|
31
31
|
|
|
32
32
|
__all__ = ["draw_series_axes", "render_figure", "style_rc"]
|
|
33
33
|
|
|
@@ -177,6 +177,8 @@ def draw_series_axes(
|
|
|
177
177
|
series_styles: Sequence[Mapping[str, Any] | None] | None = None,
|
|
178
178
|
x_fmt: Mapping[str, Any] | None = None,
|
|
179
179
|
y_fmt: Mapping[str, Any] | None = None,
|
|
180
|
+
x_step: float | None = None,
|
|
181
|
+
y_step: float | None = None,
|
|
180
182
|
) -> list[Any]:
|
|
181
183
|
"""Plot ``series`` into an EXISTING Axes: lines, scales, labels, spines,
|
|
182
184
|
legend, grid, and the per-figure override sweep (:func:`_apply_overrides`).
|
|
@@ -221,8 +223,11 @@ def draw_series_axes(
|
|
|
221
223
|
(line,) = ax.plot(xv, yv, label=label, **kw)
|
|
222
224
|
_apply_fill(ax, xv, yv, series, i, spec, line.get_color())
|
|
223
225
|
artists.append(line)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
resolved_x_scale = resolve_axis_scale(x_scale, x_log)
|
|
227
|
+
resolved_y_scale = resolve_axis_scale(y_scale, y_log)
|
|
228
|
+
apply_axis_scale(ax, "x", resolved_x_scale)
|
|
229
|
+
apply_axis_scale(ax, "y", resolved_y_scale)
|
|
230
|
+
apply_tick_steps(ax, x_step, y_step, resolved_x_scale, resolved_y_scale)
|
|
226
231
|
apply_tick_formats(ax, x_fmt, y_fmt)
|
|
227
232
|
if title:
|
|
228
233
|
ax.set_title(title)
|
|
@@ -242,9 +247,10 @@ def draw_series_axes(
|
|
|
242
247
|
loc=st.legend_location,
|
|
243
248
|
)
|
|
244
249
|
if st.grid_alpha > 0:
|
|
245
|
-
ax.grid(True, alpha=st.grid_alpha)
|
|
250
|
+
ax.grid(True, which="major", alpha=st.grid_alpha)
|
|
251
|
+
ax.grid(True, which="minor", alpha=st.grid_alpha * 0.4)
|
|
246
252
|
else:
|
|
247
|
-
ax.grid(False)
|
|
253
|
+
ax.grid(False, which="both")
|
|
248
254
|
_apply_overrides(fig, ax, st, ov, n_series=len(series))
|
|
249
255
|
return artists
|
|
250
256
|
|
|
@@ -270,6 +276,8 @@ def _render_impl(
|
|
|
270
276
|
collect_map: bool = False,
|
|
271
277
|
x_fmt: Mapping[str, Any] | None = None,
|
|
272
278
|
y_fmt: Mapping[str, Any] | None = None,
|
|
279
|
+
x_step: float | None = None,
|
|
280
|
+
y_step: float | None = None,
|
|
273
281
|
) -> bytes | dict[str, Any]:
|
|
274
282
|
"""Render ``series`` (each ``(label, y)``) against ``x`` to image bytes.
|
|
275
283
|
|
|
@@ -352,6 +360,8 @@ def _render_impl(
|
|
|
352
360
|
series_styles=series_styles,
|
|
353
361
|
x_fmt=x_fmt,
|
|
354
362
|
y_fmt=y_fmt,
|
|
363
|
+
x_step=x_step,
|
|
364
|
+
y_step=y_step,
|
|
355
365
|
)
|
|
356
366
|
fig, ax = plt.subplots(figsize=figsize)
|
|
357
367
|
try:
|
|
@@ -372,6 +382,8 @@ def _render_impl(
|
|
|
372
382
|
series_styles=series_styles,
|
|
373
383
|
x_fmt=x_fmt,
|
|
374
384
|
y_fmt=y_fmt,
|
|
385
|
+
x_step=x_step,
|
|
386
|
+
y_step=y_step,
|
|
375
387
|
)
|
|
376
388
|
if not ov.get("margins"):
|
|
377
389
|
fig.tight_layout()
|
|
@@ -40,7 +40,7 @@ from numpy.typing import ArrayLike, NDArray # noqa: E402
|
|
|
40
40
|
|
|
41
41
|
from quantized.calc.figure import _plot_kwargs # noqa: E402
|
|
42
42
|
from quantized.calc.figure_scale import apply_axis_scale, resolve_axis_scale # noqa: E402
|
|
43
|
-
from quantized.calc.figure_ticks import apply_tick_formats # noqa: E402
|
|
43
|
+
from quantized.calc.figure_ticks import apply_tick_formats, apply_tick_steps # noqa: E402
|
|
44
44
|
|
|
45
45
|
__all__ = ["render_breaks_impl"]
|
|
46
46
|
|
|
@@ -65,6 +65,8 @@ def render_breaks_impl(
|
|
|
65
65
|
series_styles: Sequence[Mapping[str, Any] | None] | None,
|
|
66
66
|
x_fmt: Mapping[str, Any] | None = None,
|
|
67
67
|
y_fmt: Mapping[str, Any] | None = None,
|
|
68
|
+
x_step: float | None = None,
|
|
69
|
+
y_step: float | None = None,
|
|
68
70
|
) -> bytes:
|
|
69
71
|
"""Render ``series`` against ``x`` with the x-axis elided over each
|
|
70
72
|
``[lo, hi]`` pair in ``breaks`` (already sorted/validated non-overlapping
|
|
@@ -100,8 +102,11 @@ def render_breaks_impl(
|
|
|
100
102
|
ax.plot(x, np.asarray(y, dtype=float), label=label, **kw)
|
|
101
103
|
lo, hi = bounds[i]
|
|
102
104
|
ax.set_xlim(lo, hi)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
resolved_x_scale = resolve_axis_scale(x_scale, x_log)
|
|
106
|
+
resolved_y_scale = resolve_axis_scale(y_scale, y_log)
|
|
107
|
+
apply_axis_scale(ax, "x", resolved_x_scale)
|
|
108
|
+
apply_axis_scale(ax, "y", resolved_y_scale)
|
|
109
|
+
apply_tick_steps(ax, x_step, y_step, resolved_x_scale, resolved_y_scale)
|
|
105
110
|
apply_tick_formats(ax, x_fmt, y_fmt)
|
|
106
111
|
if i == 0:
|
|
107
112
|
handles, labels_out = ax.get_legend_handles_labels()
|
|
@@ -113,7 +118,8 @@ def render_breaks_impl(
|
|
|
113
118
|
if not st.box_on:
|
|
114
119
|
ax.spines["top"].set_visible(False)
|
|
115
120
|
if st.grid_alpha > 0:
|
|
116
|
-
ax.grid(True, alpha=st.grid_alpha)
|
|
121
|
+
ax.grid(True, which="major", alpha=st.grid_alpha)
|
|
122
|
+
ax.grid(True, which="minor", alpha=st.grid_alpha * 0.4)
|
|
117
123
|
|
|
118
124
|
# Diagonal break glyphs (matplotlib's standard broken-axis recipe):
|
|
119
125
|
# short strokes angled across each seam, on both the outgoing panel's
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Mathtext label guard for figure export (GOTO #5 rich-text labels).
|
|
2
|
+
|
|
3
|
+
Labels flow from the UI as plain text with optional ``$...$`` mathtext
|
|
4
|
+
regions (the frontend's ``lib/richtext`` micro-syntax is a strict subset of
|
|
5
|
+
matplotlib mathtext, so passthrough is the rendering mechanism). matplotlib
|
|
6
|
+
parses math regions at DRAW time, which means an invalid label would raise
|
|
7
|
+
inside ``fig.savefig`` and turn an export into a 500. This module is the
|
|
8
|
+
shared chokepoint every ``calc.figure*`` renderer routes its user-supplied
|
|
9
|
+
strings through: it pre-validates with matplotlib's own mathtext parser and,
|
|
10
|
+
on failure, escapes the dollar signs so the label renders as literal text
|
|
11
|
+
(matplotlib renders ``\\$`` as a plain ``$``) -- an invalid label must NEVER
|
|
12
|
+
error an export.
|
|
13
|
+
|
|
14
|
+
Screen/export WYSIWYG (bug-hunt fix): the frontend's parser
|
|
15
|
+
(``frontend/src/lib/richtext.ts``) only accepts a STRICT SUBSET of
|
|
16
|
+
mathtext -- Greek letters, the symbol/relation/arrow commands in
|
|
17
|
+
``SUPPORTED_MATHTEXT_COMMANDS`` (``\\AA \\times \\leq \\approx \\infty
|
|
18
|
+
\\rightarrow`` ...), the structural commands ``\\frac \\sqrt`` (MAIN #28),
|
|
19
|
+
``\\, \\mathrm \\mathit``, and literal Unicode -- and rejects everything else
|
|
20
|
+
(e.g. ``\\hat``, ``\\vec``, ``\\overline``) as invalid markup, rendering it
|
|
21
|
+
literally on screen. Raw matplotlib accepts a much larger command set, so
|
|
22
|
+
without this gate a label like ``$\\vec{v}$`` would render literal text
|
|
23
|
+
("Invalid markup") on screen but an actual arrow-accented vector in the
|
|
24
|
+
PDF/SVG export -- a WYSIWYG violation. `_uses_only_supported_commands`
|
|
25
|
+
enumerates the SAME command set the frontend parser accepts and rejects
|
|
26
|
+
(falls back to literal) any ``$...$`` region using a command outside it,
|
|
27
|
+
BEFORE the matplotlib trial parse below ever runs. Keep
|
|
28
|
+
`SUPPORTED_MATHTEXT_COMMANDS` in sync with richtext.ts's
|
|
29
|
+
``GREEK``/``SYMBOLS``/``BIGOPS`` tables + ``mathrm``/``mathit``/``frac``/
|
|
30
|
+
``sqrt`` handling.
|
|
31
|
+
|
|
32
|
+
Pure layer: string in -> string out. matplotlib is imported lazily (same
|
|
33
|
+
convention as the figure modules -- the heavy import is paid only on export).
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import re
|
|
39
|
+
from functools import lru_cache
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
__all__ = ["safe_mathtext_label", "SUPPORTED_MATHTEXT_COMMANDS"]
|
|
43
|
+
|
|
44
|
+
# A "$" not preceded by a backslash -- matplotlib's own math-region rule
|
|
45
|
+
# (matplotlib.cbook.is_math_text counts these; an odd count means the string
|
|
46
|
+
# is rendered as literal text, dollars visible, no mathtext at all).
|
|
47
|
+
_UNESCAPED_DOLLAR = re.compile(r"(?<!\\)\$")
|
|
48
|
+
|
|
49
|
+
# A backslash command name (`\foo`) inside a math region. `\,` (thin space)
|
|
50
|
+
# is handled separately by richtext.ts before its name-based dispatch and
|
|
51
|
+
# has no letters, so it never matches this and is implicitly always allowed.
|
|
52
|
+
_COMMAND = re.compile(r"\\([a-zA-Z]+)")
|
|
53
|
+
|
|
54
|
+
# Mirrors frontend/src/lib/richtext.ts's `GREEK` table: lowercase (+ \var*
|
|
55
|
+
# variants) render italic, uppercase upright -- irrelevant here, we only
|
|
56
|
+
# need the NAME set, not the styling.
|
|
57
|
+
_GREEK_COMMANDS = frozenset(
|
|
58
|
+
{
|
|
59
|
+
"alpha", "beta", "gamma", "delta", "epsilon", "varepsilon", "zeta",
|
|
60
|
+
"eta", "theta", "vartheta", "iota", "kappa", "lambda", "mu", "nu",
|
|
61
|
+
"xi", "pi", "rho", "sigma", "varsigma", "tau", "upsilon", "phi",
|
|
62
|
+
"varphi", "chi", "psi", "omega",
|
|
63
|
+
"Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon",
|
|
64
|
+
"Phi", "Psi", "Omega",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
# Mirrors richtext.ts's `SYMBOLS` table (non-Greek symbol commands): base
|
|
68
|
+
# symbols + relations, analysis/geometry glyphs, and arrows. Each is verified
|
|
69
|
+
# to parse in matplotlib mathtext (tools probe), so a label using them renders
|
|
70
|
+
# identically on screen (richtext.ts glyph) and in export (mathtext glyph).
|
|
71
|
+
_SYMBOL_COMMANDS = frozenset(
|
|
72
|
+
{
|
|
73
|
+
"AA", "circ", "times", "cdot", "pm", "mp", "div", "prime",
|
|
74
|
+
# relations
|
|
75
|
+
"leq", "geq", "neq", "approx", "equiv", "sim", "propto", "ll", "gg",
|
|
76
|
+
# analysis / geometry
|
|
77
|
+
"infty", "partial", "nabla", "perp", "parallel", "angle",
|
|
78
|
+
"cdots", "ldots", "dots",
|
|
79
|
+
# arrows
|
|
80
|
+
"rightarrow", "to", "leftarrow", "leftrightarrow", "Rightarrow",
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
# Mirrors richtext.ts's `\mathrm{...}` / `\mathit{...}` style-group handling.
|
|
84
|
+
_STYLE_COMMANDS = frozenset({"mathrm", "mathit"})
|
|
85
|
+
# Mirrors richtext.ts's structural nodes: `\frac{a}{b}`, `\sqrt{x}`,
|
|
86
|
+
# `\sqrt[n]{x}` (MAIN #28). matplotlib renders these natively; the frontend
|
|
87
|
+
# canvas + DOM renderers draw the matching stacked/radical layout.
|
|
88
|
+
_STRUCTURE_COMMANDS = frozenset({"frac", "sqrt"})
|
|
89
|
+
# Mirrors richtext.ts's `BIGOPS` table (large operators). matplotlib stacks
|
|
90
|
+
# \sum/\prod limits over/under and side-scripts \int/\oint inline -- the
|
|
91
|
+
# frontend canvas renderer matches that limits placement.
|
|
92
|
+
_BIGOP_COMMANDS = frozenset({"sum", "prod", "int", "oint"})
|
|
93
|
+
|
|
94
|
+
SUPPORTED_MATHTEXT_COMMANDS = (
|
|
95
|
+
_GREEK_COMMANDS
|
|
96
|
+
| _SYMBOL_COMMANDS
|
|
97
|
+
| _STYLE_COMMANDS
|
|
98
|
+
| _STRUCTURE_COMMANDS
|
|
99
|
+
| _BIGOP_COMMANDS
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _uses_only_supported_commands(label: str) -> bool:
|
|
104
|
+
"""True when every backslash command inside every ``$...$`` region of
|
|
105
|
+
`label` is in `SUPPORTED_MATHTEXT_COMMANDS`. Text OUTSIDE `$...$` is
|
|
106
|
+
never scanned -- a stray backslash there is the caller's problem, not
|
|
107
|
+
this gate's (and richtext.ts itself only ever considers commands inside
|
|
108
|
+
math regions). Assumes a balanced (even) unescaped-`$` count, as
|
|
109
|
+
guaranteed by `safe_mathtext_label` calling this only after that check.
|
|
110
|
+
"""
|
|
111
|
+
segments = _UNESCAPED_DOLLAR.split(label)
|
|
112
|
+
for i, segment in enumerate(segments):
|
|
113
|
+
if i % 2 == 0:
|
|
114
|
+
continue # literal text outside $...$
|
|
115
|
+
for m in _COMMAND.finditer(segment):
|
|
116
|
+
if m.group(1) not in SUPPORTED_MATHTEXT_COMMANDS:
|
|
117
|
+
return False
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@lru_cache(maxsize=1)
|
|
122
|
+
def _parser() -> Any:
|
|
123
|
+
"""The mathtext trial parser (cached -- construction is not free)."""
|
|
124
|
+
from matplotlib.mathtext import MathTextParser
|
|
125
|
+
|
|
126
|
+
return MathTextParser("agg")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def safe_mathtext_label(label: str) -> str:
|
|
130
|
+
"""Return ``label`` unchanged when it renders safely; else de-math it.
|
|
131
|
+
|
|
132
|
+
- No ``$`` at all: fast path, returned as-is (byte-identical to today).
|
|
133
|
+
- Odd count of unescaped ``$``: matplotlib already treats the whole
|
|
134
|
+
string as literal text -- returned as-is.
|
|
135
|
+
- Balanced ``$...$`` regions using only commands the frontend's
|
|
136
|
+
richtext.ts subset also accepts (`SUPPORTED_MATHTEXT_COMMANDS`), AND
|
|
137
|
+
that trial-parse successfully with matplotlib's mathtext parser ->
|
|
138
|
+
unchanged (mathtext renders it at export, matching the screen).
|
|
139
|
+
Otherwise (an out-of-subset command, e.g. ``\\frac``/``\\sqrt``/
|
|
140
|
+
``\\sum``, OR a parse failure even within the subset, e.g. unbalanced
|
|
141
|
+
braces) -> every unescaped ``$`` is escaped to ``\\$`` so matplotlib
|
|
142
|
+
renders the literal string (dollars visible) instead of raising at
|
|
143
|
+
savefig time OR silently rendering richer math than the screen ever
|
|
144
|
+
showed.
|
|
145
|
+
"""
|
|
146
|
+
if not label or "$" not in label:
|
|
147
|
+
return label
|
|
148
|
+
n_math = len(_UNESCAPED_DOLLAR.findall(label))
|
|
149
|
+
if n_math == 0 or n_math % 2 == 1:
|
|
150
|
+
# Zero unescaped $ (all \$-escaped) or an odd count: matplotlib
|
|
151
|
+
# already renders the whole string as literal text -- leave it be.
|
|
152
|
+
return label
|
|
153
|
+
if not _uses_only_supported_commands(label):
|
|
154
|
+
return _UNESCAPED_DOLLAR.sub(r"\\$", label)
|
|
155
|
+
try:
|
|
156
|
+
_parser().parse(label)
|
|
157
|
+
except Exception: # ANY parse failure means "render literal", never raise
|
|
158
|
+
return _UNESCAPED_DOLLAR.sub(r"\\$", label)
|
|
159
|
+
return label
|
|
@@ -17,6 +17,7 @@ from collections.abc import Mapping
|
|
|
17
17
|
from typing import Any
|
|
18
18
|
|
|
19
19
|
from quantized.calc.figure_labels import safe_mathtext_label
|
|
20
|
+
from quantized.calc.figure_shapes import _apply_shapes, _validate_shapes
|
|
20
21
|
|
|
21
22
|
__all__ = ["_apply_overrides", "_validate_overrides"]
|
|
22
23
|
|
|
@@ -63,6 +64,7 @@ def _validate_overrides(ov: Mapping[str, Any]) -> None:
|
|
|
63
64
|
if prev_hi is not None and lo < prev_hi:
|
|
64
65
|
raise ValueError("x_breaks entries must be sorted and non-overlapping")
|
|
65
66
|
prev_hi = hi
|
|
67
|
+
_validate_shapes(ov.get("shapes"))
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
def _apply_overrides(
|
|
@@ -116,7 +118,7 @@ def _apply_overrides(
|
|
|
116
118
|
)
|
|
117
119
|
|
|
118
120
|
if "grid" in ov:
|
|
119
|
-
ax.grid(bool(ov["grid"]), alpha=st.grid_alpha or 0.3)
|
|
121
|
+
ax.grid(bool(ov["grid"]), which="both", alpha=st.grid_alpha or 0.3)
|
|
120
122
|
|
|
121
123
|
for ann in ov.get("annotations", []):
|
|
122
124
|
# MAIN #18: a per-annotation `size` (the pointer tool's corner-handle
|
|
@@ -142,6 +144,23 @@ def _apply_overrides(
|
|
|
142
144
|
# pixel-identical placement between screen and export.
|
|
143
145
|
ann_kw["xycoords"] = "figure fraction"
|
|
144
146
|
y = 1.0 - y
|
|
147
|
+
frame = ann.get("frame")
|
|
148
|
+
if frame:
|
|
149
|
+
# MAIN #27 "text box": a bbox behind the annotation text. `pad`
|
|
150
|
+
# (screen CSS px around the measured text) has no exact
|
|
151
|
+
# matplotlib equivalent (`boxstyle="square,pad=N"`'s pad is in
|
|
152
|
+
# font-size units, not px) -- divided by a fixed factor so the
|
|
153
|
+
# box reads as padded rather than pixel-identical, the same
|
|
154
|
+
# "Origin-parity-in-spirit, not pixel-identical" caveat the
|
|
155
|
+
# page-anchor y-flip above documents. fc/ec default to a plain
|
|
156
|
+
# white-card-on-black-border look (independent of the screen's
|
|
157
|
+
# own dark-canvas ink default -- publication export is white-bg).
|
|
158
|
+
ann_kw["bbox"] = dict(
|
|
159
|
+
boxstyle=f"square,pad={float(frame.get('pad', 4)) / 4.0}",
|
|
160
|
+
fc=frame.get("fill") or "white",
|
|
161
|
+
ec=frame.get("stroke") or "black",
|
|
162
|
+
alpha=frame.get("opacity", 1.0),
|
|
163
|
+
)
|
|
145
164
|
ax.annotate(
|
|
146
165
|
safe_mathtext_label(str(ann.get("text", ""))),
|
|
147
166
|
xy=(x, y),
|
|
@@ -157,3 +176,8 @@ def _apply_overrides(
|
|
|
157
176
|
top=None if margins.get("top") is None else 1.0 - float(margins["top"]),
|
|
158
177
|
bottom=margins.get("bottom"),
|
|
159
178
|
)
|
|
179
|
+
|
|
180
|
+
# MAIN #27: shapes paint LAST -- on top of everything else this sweep
|
|
181
|
+
# drew (annotations included), matching export intent: shapes mark up
|
|
182
|
+
# the finished figure.
|
|
183
|
+
_apply_shapes(fig, ax, ov.get("shapes"))
|
|
@@ -115,6 +115,8 @@ class PagePanel:
|
|
|
115
115
|
# (a figure page composes several independently-configured plot views).
|
|
116
116
|
x_fmt: Mapping[str, Any] | None = None
|
|
117
117
|
y_fmt: Mapping[str, Any] | None = None
|
|
118
|
+
x_step: float | None = None
|
|
119
|
+
y_step: float | None = None
|
|
118
120
|
series_styles: Sequence[Mapping[str, Any] | None] | None = None
|
|
119
121
|
overrides: Mapping[str, Any] | None = None
|
|
120
122
|
label: str | None = None
|
|
@@ -256,6 +258,8 @@ def render_figure_page(
|
|
|
256
258
|
series_styles=p.series_styles,
|
|
257
259
|
x_fmt=p.x_fmt,
|
|
258
260
|
y_fmt=p.y_fmt,
|
|
261
|
+
x_step=p.x_step,
|
|
262
|
+
y_step=p.y_step,
|
|
259
263
|
)
|
|
260
264
|
text = p.label if p.label is not None else panel_label(idx, label_format)
|
|
261
265
|
_place_label(ax, safe_mathtext_label(text), label_pos, st)
|
|
@@ -21,7 +21,7 @@ from __future__ import annotations
|
|
|
21
21
|
from typing import Any
|
|
22
22
|
|
|
23
23
|
import numpy as np
|
|
24
|
-
from matplotlib.ticker import Locator
|
|
24
|
+
from matplotlib.ticker import Locator, LogLocator, NullFormatter
|
|
25
25
|
from numpy.typing import NDArray
|
|
26
26
|
|
|
27
27
|
__all__ = ["apply_axis_scale", "reciprocal_tick_values", "resolve_axis_scale"]
|
|
@@ -115,6 +115,9 @@ def apply_axis_scale(ax: Any, axis: str, scale: str) -> None:
|
|
|
115
115
|
set_scale = ax.set_xscale if axis == "x" else ax.set_yscale
|
|
116
116
|
if scale == "log":
|
|
117
117
|
set_scale("log")
|
|
118
|
+
target = ax.xaxis if axis == "x" else ax.yaxis
|
|
119
|
+
target.set_minor_locator(LogLocator(base=10.0, subs=tuple(range(2, 10))))
|
|
120
|
+
target.set_minor_formatter(NullFormatter())
|
|
118
121
|
return
|
|
119
122
|
if scale == "reciprocal":
|
|
120
123
|
set_scale("function", functions=(_reciprocal, _reciprocal))
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Drawn-shape overrides (MAIN #27) -- export parity for the screen's
|
|
2
|
+
``lib/uplotShapes.ts``. Split out of ``figure_overrides.py`` purely to stay
|
|
3
|
+
under each module's line-count discipline (same reasoning as
|
|
4
|
+
``figure_labels.py`` / ``figure_break.py``) -- the behavioural contract is
|
|
5
|
+
still ``figure_overrides``'s; ``_apply_overrides`` calls ``_apply_shapes``
|
|
6
|
+
as its last step, AFTER the annotation sweep, so shapes paint on TOP of
|
|
7
|
+
everything else the override sweep drew (annotations included) -- matching
|
|
8
|
+
export intent: shapes mark up the finished figure. Pure layer: mapping in ->
|
|
9
|
+
mutates the passed matplotlib Figure/Axes, no return value.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Mapping, Sequence
|
|
15
|
+
from typing import Any, Literal
|
|
16
|
+
|
|
17
|
+
from matplotlib.lines import Line2D
|
|
18
|
+
from matplotlib.patches import Ellipse, FancyArrowPatch, Rectangle
|
|
19
|
+
|
|
20
|
+
__all__ = ["_apply_shapes", "_validate_shapes"]
|
|
21
|
+
|
|
22
|
+
_SHAPE_KINDS = frozenset({"arrow", "line", "rect", "ellipse"})
|
|
23
|
+
|
|
24
|
+
# Default whole-shape opacity (fill AND stroke, ONE knob) mirroring the
|
|
25
|
+
# screen's `defaultShapeOpacity` (lib/uplotShapes.ts): 1 for line/arrow, 0.35
|
|
26
|
+
# for rect/ellipse (a freshly drawn box reads as visibly translucent OVER
|
|
27
|
+
# the data it marks).
|
|
28
|
+
_DEFAULT_OPACITY = {"arrow": 1.0, "line": 1.0, "rect": 0.35, "ellipse": 0.35}
|
|
29
|
+
_DEFAULT_WIDTH = 1.5
|
|
30
|
+
_DEFAULT_STROKE = "black"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _validate_shapes(shapes: Sequence[Mapping[str, Any]] | None) -> None:
|
|
34
|
+
"""Raise ``ValueError`` on a malformed shape (bad kind, non-finite
|
|
35
|
+
coords); a shape with an unknown/missing key inside is otherwise
|
|
36
|
+
tolerant (unknown keys ignored), mirroring ``_validate_overrides``."""
|
|
37
|
+
if not shapes:
|
|
38
|
+
return
|
|
39
|
+
for sh in shapes:
|
|
40
|
+
kind = sh.get("kind")
|
|
41
|
+
if kind not in _SHAPE_KINDS:
|
|
42
|
+
raise ValueError(f"shape kind must be one of {sorted(_SHAPE_KINDS)}")
|
|
43
|
+
for key in ("x1", "y1", "x2", "y2"):
|
|
44
|
+
v = sh.get(key)
|
|
45
|
+
if not isinstance(v, (int, float)) or isinstance(v, bool):
|
|
46
|
+
raise ValueError(f"shape {key} must be a number")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _shape_endpoints(
|
|
50
|
+
sh: Mapping[str, Any], ax: Any, fig: Any
|
|
51
|
+
) -> tuple[tuple[float, float], tuple[float, float], Any]:
|
|
52
|
+
"""(p1, p2, transform) for one shape, resolving its anchor -- DATA (the
|
|
53
|
+
default: ``ax.transData``, applied implicitly by omitting ``transform``
|
|
54
|
+
and adding via ``ax.add_patch``/``ax.add_line``) or PAGE (explicit
|
|
55
|
+
``fig.transFigure``, Y FLIPPED -- canvas y grows downward, matplotlib
|
|
56
|
+
figure fraction grows upward -- the SAME convention
|
|
57
|
+
``_apply_overrides``'s page-anchored annotation uses)."""
|
|
58
|
+
x1, y1 = float(sh["x1"]), float(sh["y1"])
|
|
59
|
+
x2, y2 = float(sh["x2"]), float(sh["y2"])
|
|
60
|
+
if sh.get("anchor") == "page":
|
|
61
|
+
return (x1, 1.0 - y1), (x2, 1.0 - y2), fig.transFigure
|
|
62
|
+
return (x1, y1), (x2, y2), ax.transData
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _apply_shapes(fig: Any, ax: Any, shapes: Sequence[Mapping[str, Any]] | None) -> None:
|
|
66
|
+
"""Draw MAIN #27 shapes (arrow/line/rect/ellipse). Arrows use
|
|
67
|
+
``FancyArrowPatch``; plain lines ``Line2D``; rect/ellipse the matching
|
|
68
|
+
matplotlib patch with ``alpha`` -- all added via ``ax.add_patch``/
|
|
69
|
+
``ax.add_line`` (data-anchored, the default -- no explicit ``transform``
|
|
70
|
+
needed there; matplotlib resolves an artist added this way against
|
|
71
|
+
``ax.transData``) or with an EXPLICIT ``transform=fig.transFigure`` for
|
|
72
|
+
a page-anchored shape (``ax.add_patch``/``add_line`` never override an
|
|
73
|
+
already-set transform -- verified empirically, see the porting notes).
|
|
74
|
+
"""
|
|
75
|
+
if not shapes:
|
|
76
|
+
return
|
|
77
|
+
for sh in shapes:
|
|
78
|
+
kind = sh.get("kind")
|
|
79
|
+
if kind not in _SHAPE_KINDS:
|
|
80
|
+
continue
|
|
81
|
+
p1, p2, transform = _shape_endpoints(sh, ax, fig)
|
|
82
|
+
stroke = sh.get("stroke") or _DEFAULT_STROKE
|
|
83
|
+
# `is not None` (not `or`): an explicit `width: 0` is falsy in Python
|
|
84
|
+
# and `... or _DEFAULT_WIDTH` was silently replacing it with the
|
|
85
|
+
# default (1.5) -- unlike the correct `opacity` idiom two lines below.
|
|
86
|
+
width = float(sh["width"]) if sh.get("width") is not None else _DEFAULT_WIDTH
|
|
87
|
+
dash: Literal["-", "--"] = "--" if sh.get("dash") else "-"
|
|
88
|
+
opacity = sh.get("opacity")
|
|
89
|
+
opacity = float(opacity) if opacity is not None else _DEFAULT_OPACITY[kind]
|
|
90
|
+
fill = sh.get("fill") or stroke
|
|
91
|
+
patch: Any
|
|
92
|
+
|
|
93
|
+
if kind == "arrow":
|
|
94
|
+
patch = FancyArrowPatch(
|
|
95
|
+
p1,
|
|
96
|
+
p2,
|
|
97
|
+
transform=transform,
|
|
98
|
+
arrowstyle="-|>",
|
|
99
|
+
mutation_scale=12 + width * 4,
|
|
100
|
+
# matplotlib's FancyArrowPatch default shrinkA/shrinkB=2pt
|
|
101
|
+
# insets BOTH ends from the given points (meant for
|
|
102
|
+
# "annotate near, not touching, an object") -- WYSIWYG with
|
|
103
|
+
# the screen's canvas draw (no shrink at all) needs the
|
|
104
|
+
# endpoints to land EXACTLY where dragged.
|
|
105
|
+
shrinkA=0,
|
|
106
|
+
shrinkB=0,
|
|
107
|
+
color=stroke,
|
|
108
|
+
linewidth=width,
|
|
109
|
+
linestyle=dash,
|
|
110
|
+
alpha=opacity,
|
|
111
|
+
)
|
|
112
|
+
ax.add_patch(patch)
|
|
113
|
+
elif kind == "line":
|
|
114
|
+
line = Line2D(
|
|
115
|
+
[p1[0], p2[0]],
|
|
116
|
+
[p1[1], p2[1]],
|
|
117
|
+
transform=transform,
|
|
118
|
+
color=stroke,
|
|
119
|
+
linewidth=width,
|
|
120
|
+
linestyle=dash,
|
|
121
|
+
alpha=opacity,
|
|
122
|
+
)
|
|
123
|
+
ax.add_line(line)
|
|
124
|
+
elif kind == "rect":
|
|
125
|
+
x0, x1v = min(p1[0], p2[0]), max(p1[0], p2[0])
|
|
126
|
+
y0, y1v = min(p1[1], p2[1]), max(p1[1], p2[1])
|
|
127
|
+
patch = Rectangle(
|
|
128
|
+
(x0, y0),
|
|
129
|
+
x1v - x0,
|
|
130
|
+
y1v - y0,
|
|
131
|
+
transform=transform,
|
|
132
|
+
edgecolor=stroke,
|
|
133
|
+
facecolor=fill,
|
|
134
|
+
linewidth=width,
|
|
135
|
+
linestyle=dash,
|
|
136
|
+
alpha=opacity,
|
|
137
|
+
)
|
|
138
|
+
ax.add_patch(patch)
|
|
139
|
+
else: # ellipse
|
|
140
|
+
cx, cy = (p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2
|
|
141
|
+
w, h = abs(p2[0] - p1[0]), abs(p2[1] - p1[1])
|
|
142
|
+
patch = Ellipse(
|
|
143
|
+
(cx, cy),
|
|
144
|
+
w,
|
|
145
|
+
h,
|
|
146
|
+
transform=transform,
|
|
147
|
+
edgecolor=stroke,
|
|
148
|
+
facecolor=fill,
|
|
149
|
+
linewidth=width,
|
|
150
|
+
linestyle=dash,
|
|
151
|
+
alpha=opacity,
|
|
152
|
+
)
|
|
153
|
+
ax.add_patch(patch)
|
|
@@ -36,9 +36,9 @@ import math
|
|
|
36
36
|
from collections.abc import Mapping
|
|
37
37
|
from typing import Any
|
|
38
38
|
|
|
39
|
-
from matplotlib.ticker import Formatter
|
|
39
|
+
from matplotlib.ticker import Formatter, MultipleLocator
|
|
40
40
|
|
|
41
|
-
__all__ = ["apply_tick_formats", "axis_tick_formatter"]
|
|
41
|
+
__all__ = ["apply_tick_formats", "apply_tick_steps", "axis_tick_formatter"]
|
|
42
42
|
|
|
43
43
|
_MODES = ("fixed", "sci", "eng")
|
|
44
44
|
|
|
@@ -215,3 +215,32 @@ def apply_tick_formats(
|
|
|
215
215
|
yf = axis_tick_formatter(y_fmt)
|
|
216
216
|
if yf is not None:
|
|
217
217
|
ax.yaxis.set_major_formatter(yf)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def apply_tick_steps(
|
|
221
|
+
ax: Any,
|
|
222
|
+
x_step: float | None,
|
|
223
|
+
y_step: float | None,
|
|
224
|
+
x_scale: str,
|
|
225
|
+
y_scale: str,
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Apply saved major-tick increments to linear axes only.
|
|
228
|
+
|
|
229
|
+
Origin's decoded ``from/to/step`` triples carry a linear increment. Log
|
|
230
|
+
axes retain their scale-specific locator; reciprocal axes have their own
|
|
231
|
+
locator. Invalid or absent increments leave matplotlib's locator intact.
|
|
232
|
+
``MultipleLocator`` anchors ticks at integer multiples of the step, the
|
|
233
|
+
same convention as the interactive ``fixedLinearAxisSplits`` helper.
|
|
234
|
+
"""
|
|
235
|
+
def apply_one(axis: Any, limits: tuple[float, float], step: float | None) -> None:
|
|
236
|
+
if step is None or not math.isfinite(step) or step <= 0:
|
|
237
|
+
return
|
|
238
|
+
span = abs(float(limits[1]) - float(limits[0]))
|
|
239
|
+
if not math.isfinite(span) or span / step > 1000:
|
|
240
|
+
return
|
|
241
|
+
axis.set_major_locator(MultipleLocator(float(step)))
|
|
242
|
+
|
|
243
|
+
if x_scale == "linear":
|
|
244
|
+
apply_one(ax.xaxis, ax.get_xlim(), x_step)
|
|
245
|
+
if y_scale == "linear":
|
|
246
|
+
apply_one(ax.yaxis, ax.get_ylim(), y_step)
|
|
@@ -39,6 +39,12 @@ AbortFn = Callable[[], bool]
|
|
|
39
39
|
#: Supported bumps engine ids (bumps.fitters FITTERS ids).
|
|
40
40
|
BUMPS_ENGINES = ("amoeba", "lm", "de", "dream")
|
|
41
41
|
|
|
42
|
+
#: Gelman-Rubin R-hat at/above which DREAM chains are considered NOT converged
|
|
43
|
+
#: (the widely-used 1.1 threshold; < ~1.01 is "well mixed"). Surfaced so the UI
|
|
44
|
+
#: never presents posterior intervals as trustworthy when the chains haven't
|
|
45
|
+
#: mixed (audit P1 #2).
|
|
46
|
+
RHAT_THRESHOLD = 1.1
|
|
47
|
+
|
|
42
48
|
_INSTALL_HINT = (
|
|
43
49
|
"bumps is not installed - the optional fit engine needs "
|
|
44
50
|
"'pip install quantized[bumps]' (or: uv sync --extra bumps)"
|
|
@@ -154,9 +160,11 @@ def fit_bumps(
|
|
|
154
160
|
saved equation-builder models) or a raw ``f(x, p)`` callable. ``engine``
|
|
155
161
|
is one of ``BUMPS_ENGINES``: amoeba / lm / de report Hessian-derived
|
|
156
162
|
uncertainties (``uncertainty_kind='hessian'``); dream samples the
|
|
157
|
-
posterior (``uncertainty_kind='posterior'``) and additionally returns
|
|
158
|
-
per-parameter medians
|
|
159
|
-
|
|
163
|
+
posterior (``uncertainty_kind='posterior'``) and additionally returns, in
|
|
164
|
+
``result['posterior']``, per-parameter medians, central 68% intervals,
|
|
165
|
+
``n_draws``, ``nChains``, and the convergence diagnostics ``rHat`` (per
|
|
166
|
+
parameter), ``rHatMax``, and ``converged`` (Gelman-Rubin, audit P1 #2) —
|
|
167
|
+
plus the raw draw matrix when ``return_samples`` (corner-plot food).
|
|
160
168
|
|
|
161
169
|
``samples`` / ``burn`` / ``pop`` tune dream only. ``progress_callback``
|
|
162
170
|
(fraction in [0, 1)) and ``abort_check`` (True -> stop sampling early)
|
|
@@ -243,15 +251,33 @@ def fit_bumps(
|
|
|
243
251
|
}
|
|
244
252
|
|
|
245
253
|
if engine == "dream":
|
|
246
|
-
|
|
254
|
+
state = driver.fitter.state
|
|
255
|
+
draw = state.draw()
|
|
247
256
|
pts = np.asarray(draw.points, dtype=float)[:, order]
|
|
248
257
|
lo68, med, hi68 = (
|
|
249
258
|
np.asarray(np.percentile(pts, q, axis=0), dtype=float) for q in (16.0, 50.0, 84.0)
|
|
250
259
|
)
|
|
260
|
+
# Convergence diagnostics (audit P1 #2): the Gelman-Rubin potential
|
|
261
|
+
# scale reduction factor (R-hat) per parameter, so posterior intervals
|
|
262
|
+
# are never presented as trustworthy when the chains haven't mixed.
|
|
263
|
+
# (bumps also warns "Did not converge!" via a separate K-S test; R-hat
|
|
264
|
+
# is the standard, interpretable metric. bumps exposes no clean
|
|
265
|
+
# effective-sample-size, so ESS isn't faked from cross-chain draws.)
|
|
266
|
+
try:
|
|
267
|
+
rhat_all = np.asarray(state.gelman(), dtype=float)
|
|
268
|
+
rhat = [float(rhat_all[i]) for i in order]
|
|
269
|
+
rhat_max = float(np.max(rhat_all)) if rhat_all.size else float("nan")
|
|
270
|
+
except Exception: # noqa: BLE001 — aborted/degenerate state (cf. stderr above)
|
|
271
|
+
rhat = [float("nan")] * n_params
|
|
272
|
+
rhat_max = float("nan")
|
|
251
273
|
result["posterior"] = {
|
|
252
274
|
"medians": [float(v) for v in med],
|
|
253
275
|
"interval68": [[float(a), float(b)] for a, b in zip(lo68, hi68, strict=True)],
|
|
254
276
|
"n_draws": int(pts.shape[0]),
|
|
277
|
+
"rHat": rhat,
|
|
278
|
+
"rHatMax": rhat_max,
|
|
279
|
+
"converged": bool(math.isfinite(rhat_max) and rhat_max < RHAT_THRESHOLD),
|
|
280
|
+
"nChains": int(getattr(state, "Npop", pop * n_params)),
|
|
255
281
|
}
|
|
256
282
|
if return_samples:
|
|
257
283
|
result["samples"] = pts
|