quantized-lab 0.8.0__tar.gz → 0.9.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.9.0/PKG-INFO +176 -0
- quantized_lab-0.9.0/README.md +137 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/pyproject.toml +9 -2
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/__init__.py +1 -1
- quantized_lab-0.9.0/src/quantized/calc/figure_labels.py +127 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_overrides.py +24 -0
- quantized_lab-0.9.0/src/quantized/calc/figure_shapes.py +153 -0
- quantized_lab-0.9.0/src/quantized/web/assets/index-D8RNGjHO.js +27 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/index.html +1 -1
- quantized_lab-0.8.0/PKG-INFO +0 -122
- quantized_lab-0.8.0/README.md +0 -88
- quantized_lab-0.8.0/src/quantized/calc/figure_labels.py +0 -62
- quantized_lab-0.8.0/src/quantized/web/assets/index-BHmmCL-x.js +0 -27
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/.gitignore +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/LICENSE +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/NOTICE +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/__main__.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/api.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/app.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/__init__.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/_clipfit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/_natural_neighbor.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/aggregate.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/backgrounds.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/baseline.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/batch_fit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/constants.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/corrections.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/crystallography.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/diffusion.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/electrical.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/electrochemistry.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/element_data.json +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/element_data.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/errors.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_break.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_categorical.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_corner.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_facets.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_field.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_hitmap.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_map.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_page.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_scale.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_statplots.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_styles.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_ternary.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_ticks.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_autoguess.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_bootstrap.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_bumps.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_constraints.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_equation.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_findxy.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_models.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_models_special.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_odr.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_scan.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_stats.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fitting.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/formula.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/global_curve_fit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/global_fit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/interp2d.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/linecut.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/magnetic.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/magnetometry.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/map.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/mcmc.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/optics.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/pawley.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_batch.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_fit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_integrate.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_multifit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_track.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peaks.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peakshapes.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/plotting.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/processing.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/qspace.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/reductions.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/reductions_fft.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/refl_sld_presets.json +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/reflectivity.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/registry.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/relaxation.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/report.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/report_emit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/resample.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/rsm.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/rsm_analyze.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/semiconductor.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/sld.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/sld_formula.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/spectral.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/statplots.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_anova2.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_anova_ext.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_dist.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_glm.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_multivar.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_roc.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_survival.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_tests.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/substrates.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/superconductor.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/surface_fit.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/surface_models.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/thermal.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/thin_film.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/unit_convert.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/units.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/vacuum.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/xray.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/cli.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/datastruct.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/__init__.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/_hdf5_layout.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/_jcamp_asdf.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/_xrdml_scan.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/base.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/bruker_brml.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/bruker_raw.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/cif.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/consolidated.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/delimited.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/excel.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/hdf5.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/import_filters.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/import_preview.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/jcamp.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/lakeshore.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/ncnr.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/netcdf.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/opus.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_com.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/__init__.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/annotation_marks.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/container.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/curve_style_color.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figure_geometry.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figure_layers.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figure_text.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figures.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figures_opju.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/notes.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opj.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opj_curves.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opj_shapes.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_axis_real_form.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_axis_specimen_form.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_codec.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_curves.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_curves_allcols.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_figure_curves.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_figure_text.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_reports.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/origin_richtext.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/preview.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/templates.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/tree.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/tree_opju.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/windows.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/windows_opju.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/writer.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/writer_blocks.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/qd.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/refl1d.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/registry.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/report_export.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/rigaku.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/sims.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/spc.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/xrd_csv.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/xrdml.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/jobs.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/__init__.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/contract.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/loader.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/steps.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/__init__.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_bookcache.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_export_common.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_payload.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_uploadcache.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/aggregate.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/baseline.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/books.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/calc.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/corrections.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/crystallography.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/diffusion.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/electrical.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/electrochemistry.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export_facets.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export_figures.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export_page.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/fitting.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/fitting_bumps.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/import_template.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/import_wizard.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/jobs_api.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/magnetic.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/magnetometry.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/optics.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/parsers.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/peaks.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/plot.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/reductions.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/reference.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/reflectivity.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/report_export.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/rsm.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/samples.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/semiconductor.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/sld.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/spectral.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/statplots.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/stats.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/stats_design.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/substrates.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/superconductor.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/thermal.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/thin_film.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/vacuum.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/xray.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/samples/demo_vsm.csv +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/server_launch.py +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/assets/index-BiZzN7J6.css +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/loading.html +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/tools/origin_compare/README.md +0 -0
- {quantized_lab-0.8.0 → quantized_lab-0.9.0}/tools/visual/README.md +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quantized-lab
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Plot, analyze, and publish lab data - magnetometry, XRD, reflectometry - with OriginPro file interop, golden-verified analysis, and reproducible pipelines.
|
|
5
|
+
Project-URL: Homepage, https://github.com/pquarterman17/quantized
|
|
6
|
+
Project-URL: Documentation, https://github.com/pquarterman17/quantized/wiki
|
|
7
|
+
Project-URL: Repository, https://github.com/pquarterman17/quantized
|
|
8
|
+
Project-URL: Releases, https://github.com/pquarterman17/quantized/releases
|
|
9
|
+
Project-URL: Issues, https://github.com/pquarterman17/quantized/issues
|
|
10
|
+
Author: Paige Quarterman
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
License-File: NOTICE
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: fastapi>=0.139.0
|
|
16
|
+
Requires-Dist: h5py>=3.10
|
|
17
|
+
Requires-Dist: matplotlib>=3.11.0
|
|
18
|
+
Requires-Dist: numpy>=1.26
|
|
19
|
+
Requires-Dist: openpyxl>=3.1
|
|
20
|
+
Requires-Dist: periodictable>=1.7
|
|
21
|
+
Requires-Dist: platformdirs>=4.10.0
|
|
22
|
+
Requires-Dist: pydantic>=2.7
|
|
23
|
+
Requires-Dist: python-multipart>=0.0.32
|
|
24
|
+
Requires-Dist: scipy>=1.11
|
|
25
|
+
Requires-Dist: uvicorn[standard]>=0.51.0
|
|
26
|
+
Provides-Extra: bumps
|
|
27
|
+
Requires-Dist: bumps>=1.0; extra == 'bumps'
|
|
28
|
+
Provides-Extra: desktop
|
|
29
|
+
Requires-Dist: pywebview>=5.1; extra == 'desktop'
|
|
30
|
+
Provides-Extra: office
|
|
31
|
+
Requires-Dist: python-docx>=1.1; extra == 'office'
|
|
32
|
+
Requires-Dist: python-pptx>=1.0; extra == 'office'
|
|
33
|
+
Provides-Extra: origin-com
|
|
34
|
+
Requires-Dist: pywin32; (sys_platform == 'win32') and extra == 'origin-com'
|
|
35
|
+
Provides-Extra: stats
|
|
36
|
+
Requires-Dist: lifelines>=0.27; extra == 'stats'
|
|
37
|
+
Requires-Dist: statsmodels>=0.14; extra == 'stats'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# quantized
|
|
41
|
+
|
|
42
|
+
**Plot, analyze, and publish lab data — magnetometry, X-ray/neutron
|
|
43
|
+
diffraction & reflectometry, transport — in one fast, reproducible app.**
|
|
44
|
+
|
|
45
|
+
A clean-architecture Python (FastAPI) + React application, born as a port
|
|
46
|
+
of the `quantized_matlab` scientific toolbox and grown into a full
|
|
47
|
+
OriginPro-class daily driver. Backend math is **golden-verified** against
|
|
48
|
+
the MATLAB original; the GUI is a ground-up revamp.
|
|
49
|
+
|
|
50
|
+
> **Install:** `pip install quantized-lab` → run `qz`
|
|
51
|
+
> (the PyPI name is `quantized-lab`; the package you import and the
|
|
52
|
+
> commands you run are plain `quantized` / `qz`)
|
|
53
|
+
|
|
54
|
+
## What it does
|
|
55
|
+
|
|
56
|
+
**Import instrument data** — Quantum Design VSM/PPMS/MPMS, Lake Shore VSM,
|
|
57
|
+
PANalytical XRDML (1-D scans *and* 2-D reciprocal-space maps with Q-space),
|
|
58
|
+
Rigaku, NCNR reflectometry/PNR, refl1d, SIMS, generic CSV/Excel — with
|
|
59
|
+
content sniffing for ambiguous extensions, drag-drop, paste, guided import
|
|
60
|
+
wizard, and one-click re-import when the source file changes.
|
|
61
|
+
|
|
62
|
+
**Read and write OriginPro files** — native, clean-room `.opj`/`.opju`
|
|
63
|
+
readers recover workbooks, figures (with styles, legends, annotations),
|
|
64
|
+
and notes; the `.opj` writer produces projects real Origin loads; plus
|
|
65
|
+
Origin-ASCII + `.ogs` scripts, `.otp` template import, and an optional
|
|
66
|
+
Windows COM "Send to Origin" bridge.
|
|
67
|
+
|
|
68
|
+
**Explore interactively** — an Origin-style pointer tool (drag, resize,
|
|
69
|
+
and pin plot text; draggable legends), multi-window MDI with linked
|
|
70
|
+
zoom/cursor groups, a ctrl-click panel builder (side-by-side / grid /
|
|
71
|
+
overlay with automatic dual-Y by unit family), linked row selection and
|
|
72
|
+
masking across every view, worksheet with formulas and filters, and
|
|
73
|
+
app-wide **undo/redo**.
|
|
74
|
+
|
|
75
|
+
**Analyze with domain depth** — 29+ fit models plus a no-eval custom
|
|
76
|
+
equation builder, AICc model scanning, global/batch/ODR fits, an optional
|
|
77
|
+
[bumps](https://github.com/bumps/bumps) engine with DREAM posterior
|
|
78
|
+
sampling; a Peak Analyzer wizard; baselines from ALS to Shirley to
|
|
79
|
+
interactive anchor points; Parratt reflectivity; RSM strain analysis;
|
|
80
|
+
Williamson-Hall, FFT film thickness, and reflectivity-FFT reductions;
|
|
81
|
+
hysteresis, relaxation, and a full statistics suite.
|
|
82
|
+
|
|
83
|
+
**Publish** — vector PDF/SVG export (server-rendered matplotlib) that
|
|
84
|
+
matches the screen: rich-text labels (µ₀H, Å⁻¹, χ″…), journal size
|
|
85
|
+
presets (APS/Nature/thesis…), per-series styles, fills, color-mapped
|
|
86
|
+
scatter, engineering/scientific tick formats, and a multi-panel figure
|
|
87
|
+
composer for Fig. 1(a)–(d)-style pages.
|
|
88
|
+
|
|
89
|
+
**Stay reproducible** — every correction is a replayable pipeline step
|
|
90
|
+
with a recalculation DAG, a macro recorder emits runnable scripts,
|
|
91
|
+
workspaces save to a single `.dwk` file, and everything is scriptable
|
|
92
|
+
through the HTTP API and a plugin system.
|
|
93
|
+
|
|
94
|
+
**DiraCulator** — the built-in materials calculators (units, X-ray/neutron,
|
|
95
|
+
crystallography, SLD, semiconductors, superconductors, magnetism, optics,
|
|
96
|
+
vacuum, and more) also launch standalone: `diraculator` (or
|
|
97
|
+
`qz --calc`), with its own Start Menu entry in the Windows installer.
|
|
98
|
+
|
|
99
|
+
## Scope
|
|
100
|
+
|
|
101
|
+
Magnetometry / XRD / reflectometry / lab-data analysis. Electron-microscopy
|
|
102
|
+
tooling (EELS/EDS, imaging) is **out of scope** and lives in the separate
|
|
103
|
+
`fermiviewer` project.
|
|
104
|
+
|
|
105
|
+
## Run it
|
|
106
|
+
|
|
107
|
+
| Method | Command | Notes |
|
|
108
|
+
|--------|---------|-------|
|
|
109
|
+
| pipx (recommended) | `pipx install quantized-lab && qz` | isolated env, `qz` on PATH, no dev tools needed |
|
|
110
|
+
| uv tool | `uv tool install quantized-lab && qz` | same idea, via `uv` |
|
|
111
|
+
| pip | `pip install quantized-lab && qz` | into whatever env is active |
|
|
112
|
+
| Native installer | download from [Releases](https://github.com/pquarterman17/quantized/releases) | Windows `.exe` (NSIS, auto-updates), macOS `.dmg`, Linux `.deb` — no Python required |
|
|
113
|
+
| From source | see below | for development |
|
|
114
|
+
|
|
115
|
+
Once installed, `qz` serves the app at `http://127.0.0.1:8000` and opens a
|
|
116
|
+
browser tab. An empty library shows a **"Drop files here, or use ⊞ to import
|
|
117
|
+
/ ✚ for a demo"** hint — click **✚** for an instant synthetic dataset, so a
|
|
118
|
+
fresh install has something to plot within seconds.
|
|
119
|
+
|
|
120
|
+
**From source — double-click a launcher** (builds the UI + installs deps on
|
|
121
|
+
first run, then opens the app in your browser):
|
|
122
|
+
|
|
123
|
+
- **Windows:** double-click `run.cmd`
|
|
124
|
+
- **macOS:** double-click `run.command` (first time: right-click → Open)
|
|
125
|
+
|
|
126
|
+
**Or one command** (after `uv` + Node.js are installed):
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# first time only: build the UI bundle the app serves
|
|
130
|
+
cd frontend && npm install && npm run build && cd ..
|
|
131
|
+
|
|
132
|
+
uv run qz # serve on :8000 and open a browser tab
|
|
133
|
+
uv run qz --port 9000 # different port
|
|
134
|
+
uv run qz --no-browser # headless (don't open a tab; never auto-exits)
|
|
135
|
+
uv run qz --desktop # native window (needs: pip install quantized-lab[desktop])
|
|
136
|
+
uv run qz --dev # contributor mode: Vite HMR + reloading backend
|
|
137
|
+
uv run diraculator # the materials calculators, standalone
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The default mode behaves like an app: closing the last browser tab shuts
|
|
141
|
+
the server down (a page refresh doesn't). Set `QZ_AUTO_SHUTDOWN=0` to opt
|
|
142
|
+
out, use `--no-browser` for a persistent/headless server, or stop any mode
|
|
143
|
+
with **Ctrl+C** in its window. A busy port falls back to a free one
|
|
144
|
+
automatically, so the app and a standalone DiraCulator coexist.
|
|
145
|
+
|
|
146
|
+
Building your own wheel/sdist? Build the frontend **first** —
|
|
147
|
+
`cd frontend && npm ci && npm run build` — before `uv build`, or the wheel
|
|
148
|
+
ships without a UI.
|
|
149
|
+
|
|
150
|
+
## Documentation
|
|
151
|
+
|
|
152
|
+
- **[Wiki](https://github.com/pquarterman17/quantized/wiki)** — installation,
|
|
153
|
+
getting started, feature guides, Origin interop
|
|
154
|
+
- **[Releases](https://github.com/pquarterman17/quantized/releases)** —
|
|
155
|
+
installers + changelogs
|
|
156
|
+
- [`RELEASE.md`](https://github.com/pquarterman17/quantized/blob/main/RELEASE.md)
|
|
157
|
+
— how releases are built and published
|
|
158
|
+
- In-app: **⌘K** command palette, **Help ▸ Keyboard shortcuts**, and
|
|
159
|
+
**Help ▸ Text formatting** for the rich-text label syntax
|
|
160
|
+
|
|
161
|
+
## Architecture (for contributors)
|
|
162
|
+
|
|
163
|
+
Pure `io/` + `calc/` libraries (data in → results out, no web imports),
|
|
164
|
+
thin FastAPI `routes/`, a 500-line per-module ceiling, ~400-line React
|
|
165
|
+
component ceiling, and **golden tests** that freeze MATLAB outputs as the
|
|
166
|
+
parity oracle — enforced by tests, not convention. See
|
|
167
|
+
[`CLAUDE.md`](https://github.com/pquarterman17/quantized/blob/main/CLAUDE.md).
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
uv sync --group dev # backend deps
|
|
171
|
+
uv run pytest # backend tests (+ `-m golden` for MATLAB parity)
|
|
172
|
+
uv run ruff check src tests && uv run mypy src
|
|
173
|
+
cd frontend && npm test && npm run build
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
License: Apache-2.0.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# quantized
|
|
2
|
+
|
|
3
|
+
**Plot, analyze, and publish lab data — magnetometry, X-ray/neutron
|
|
4
|
+
diffraction & reflectometry, transport — in one fast, reproducible app.**
|
|
5
|
+
|
|
6
|
+
A clean-architecture Python (FastAPI) + React application, born as a port
|
|
7
|
+
of the `quantized_matlab` scientific toolbox and grown into a full
|
|
8
|
+
OriginPro-class daily driver. Backend math is **golden-verified** against
|
|
9
|
+
the MATLAB original; the GUI is a ground-up revamp.
|
|
10
|
+
|
|
11
|
+
> **Install:** `pip install quantized-lab` → run `qz`
|
|
12
|
+
> (the PyPI name is `quantized-lab`; the package you import and the
|
|
13
|
+
> commands you run are plain `quantized` / `qz`)
|
|
14
|
+
|
|
15
|
+
## What it does
|
|
16
|
+
|
|
17
|
+
**Import instrument data** — Quantum Design VSM/PPMS/MPMS, Lake Shore VSM,
|
|
18
|
+
PANalytical XRDML (1-D scans *and* 2-D reciprocal-space maps with Q-space),
|
|
19
|
+
Rigaku, NCNR reflectometry/PNR, refl1d, SIMS, generic CSV/Excel — with
|
|
20
|
+
content sniffing for ambiguous extensions, drag-drop, paste, guided import
|
|
21
|
+
wizard, and one-click re-import when the source file changes.
|
|
22
|
+
|
|
23
|
+
**Read and write OriginPro files** — native, clean-room `.opj`/`.opju`
|
|
24
|
+
readers recover workbooks, figures (with styles, legends, annotations),
|
|
25
|
+
and notes; the `.opj` writer produces projects real Origin loads; plus
|
|
26
|
+
Origin-ASCII + `.ogs` scripts, `.otp` template import, and an optional
|
|
27
|
+
Windows COM "Send to Origin" bridge.
|
|
28
|
+
|
|
29
|
+
**Explore interactively** — an Origin-style pointer tool (drag, resize,
|
|
30
|
+
and pin plot text; draggable legends), multi-window MDI with linked
|
|
31
|
+
zoom/cursor groups, a ctrl-click panel builder (side-by-side / grid /
|
|
32
|
+
overlay with automatic dual-Y by unit family), linked row selection and
|
|
33
|
+
masking across every view, worksheet with formulas and filters, and
|
|
34
|
+
app-wide **undo/redo**.
|
|
35
|
+
|
|
36
|
+
**Analyze with domain depth** — 29+ fit models plus a no-eval custom
|
|
37
|
+
equation builder, AICc model scanning, global/batch/ODR fits, an optional
|
|
38
|
+
[bumps](https://github.com/bumps/bumps) engine with DREAM posterior
|
|
39
|
+
sampling; a Peak Analyzer wizard; baselines from ALS to Shirley to
|
|
40
|
+
interactive anchor points; Parratt reflectivity; RSM strain analysis;
|
|
41
|
+
Williamson-Hall, FFT film thickness, and reflectivity-FFT reductions;
|
|
42
|
+
hysteresis, relaxation, and a full statistics suite.
|
|
43
|
+
|
|
44
|
+
**Publish** — vector PDF/SVG export (server-rendered matplotlib) that
|
|
45
|
+
matches the screen: rich-text labels (µ₀H, Å⁻¹, χ″…), journal size
|
|
46
|
+
presets (APS/Nature/thesis…), per-series styles, fills, color-mapped
|
|
47
|
+
scatter, engineering/scientific tick formats, and a multi-panel figure
|
|
48
|
+
composer for Fig. 1(a)–(d)-style pages.
|
|
49
|
+
|
|
50
|
+
**Stay reproducible** — every correction is a replayable pipeline step
|
|
51
|
+
with a recalculation DAG, a macro recorder emits runnable scripts,
|
|
52
|
+
workspaces save to a single `.dwk` file, and everything is scriptable
|
|
53
|
+
through the HTTP API and a plugin system.
|
|
54
|
+
|
|
55
|
+
**DiraCulator** — the built-in materials calculators (units, X-ray/neutron,
|
|
56
|
+
crystallography, SLD, semiconductors, superconductors, magnetism, optics,
|
|
57
|
+
vacuum, and more) also launch standalone: `diraculator` (or
|
|
58
|
+
`qz --calc`), with its own Start Menu entry in the Windows installer.
|
|
59
|
+
|
|
60
|
+
## Scope
|
|
61
|
+
|
|
62
|
+
Magnetometry / XRD / reflectometry / lab-data analysis. Electron-microscopy
|
|
63
|
+
tooling (EELS/EDS, imaging) is **out of scope** and lives in the separate
|
|
64
|
+
`fermiviewer` project.
|
|
65
|
+
|
|
66
|
+
## Run it
|
|
67
|
+
|
|
68
|
+
| Method | Command | Notes |
|
|
69
|
+
|--------|---------|-------|
|
|
70
|
+
| pipx (recommended) | `pipx install quantized-lab && qz` | isolated env, `qz` on PATH, no dev tools needed |
|
|
71
|
+
| uv tool | `uv tool install quantized-lab && qz` | same idea, via `uv` |
|
|
72
|
+
| pip | `pip install quantized-lab && qz` | into whatever env is active |
|
|
73
|
+
| Native installer | download from [Releases](https://github.com/pquarterman17/quantized/releases) | Windows `.exe` (NSIS, auto-updates), macOS `.dmg`, Linux `.deb` — no Python required |
|
|
74
|
+
| From source | see below | for development |
|
|
75
|
+
|
|
76
|
+
Once installed, `qz` serves the app at `http://127.0.0.1:8000` and opens a
|
|
77
|
+
browser tab. An empty library shows a **"Drop files here, or use ⊞ to import
|
|
78
|
+
/ ✚ for a demo"** hint — click **✚** for an instant synthetic dataset, so a
|
|
79
|
+
fresh install has something to plot within seconds.
|
|
80
|
+
|
|
81
|
+
**From source — double-click a launcher** (builds the UI + installs deps on
|
|
82
|
+
first run, then opens the app in your browser):
|
|
83
|
+
|
|
84
|
+
- **Windows:** double-click `run.cmd`
|
|
85
|
+
- **macOS:** double-click `run.command` (first time: right-click → Open)
|
|
86
|
+
|
|
87
|
+
**Or one command** (after `uv` + Node.js are installed):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# first time only: build the UI bundle the app serves
|
|
91
|
+
cd frontend && npm install && npm run build && cd ..
|
|
92
|
+
|
|
93
|
+
uv run qz # serve on :8000 and open a browser tab
|
|
94
|
+
uv run qz --port 9000 # different port
|
|
95
|
+
uv run qz --no-browser # headless (don't open a tab; never auto-exits)
|
|
96
|
+
uv run qz --desktop # native window (needs: pip install quantized-lab[desktop])
|
|
97
|
+
uv run qz --dev # contributor mode: Vite HMR + reloading backend
|
|
98
|
+
uv run diraculator # the materials calculators, standalone
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The default mode behaves like an app: closing the last browser tab shuts
|
|
102
|
+
the server down (a page refresh doesn't). Set `QZ_AUTO_SHUTDOWN=0` to opt
|
|
103
|
+
out, use `--no-browser` for a persistent/headless server, or stop any mode
|
|
104
|
+
with **Ctrl+C** in its window. A busy port falls back to a free one
|
|
105
|
+
automatically, so the app and a standalone DiraCulator coexist.
|
|
106
|
+
|
|
107
|
+
Building your own wheel/sdist? Build the frontend **first** —
|
|
108
|
+
`cd frontend && npm ci && npm run build` — before `uv build`, or the wheel
|
|
109
|
+
ships without a UI.
|
|
110
|
+
|
|
111
|
+
## Documentation
|
|
112
|
+
|
|
113
|
+
- **[Wiki](https://github.com/pquarterman17/quantized/wiki)** — installation,
|
|
114
|
+
getting started, feature guides, Origin interop
|
|
115
|
+
- **[Releases](https://github.com/pquarterman17/quantized/releases)** —
|
|
116
|
+
installers + changelogs
|
|
117
|
+
- [`RELEASE.md`](https://github.com/pquarterman17/quantized/blob/main/RELEASE.md)
|
|
118
|
+
— how releases are built and published
|
|
119
|
+
- In-app: **⌘K** command palette, **Help ▸ Keyboard shortcuts**, and
|
|
120
|
+
**Help ▸ Text formatting** for the rich-text label syntax
|
|
121
|
+
|
|
122
|
+
## Architecture (for contributors)
|
|
123
|
+
|
|
124
|
+
Pure `io/` + `calc/` libraries (data in → results out, no web imports),
|
|
125
|
+
thin FastAPI `routes/`, a 500-line per-module ceiling, ~400-line React
|
|
126
|
+
component ceiling, and **golden tests** that freeze MATLAB outputs as the
|
|
127
|
+
parity oracle — enforced by tests, not convention. See
|
|
128
|
+
[`CLAUDE.md`](https://github.com/pquarterman17/quantized/blob/main/CLAUDE.md).
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
uv sync --group dev # backend deps
|
|
132
|
+
uv run pytest # backend tests (+ `-m golden` for MATLAB parity)
|
|
133
|
+
uv run ruff check src tests && uv run mypy src
|
|
134
|
+
cd frontend && npm test && npm run build
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
License: Apache-2.0.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "quantized-lab"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.9.0"
|
|
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"
|
|
7
7
|
license = "Apache-2.0"
|
|
@@ -20,6 +20,13 @@ dependencies = [
|
|
|
20
20
|
"platformdirs>=4.10.0",
|
|
21
21
|
]
|
|
22
22
|
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/pquarterman17/quantized"
|
|
25
|
+
Documentation = "https://github.com/pquarterman17/quantized/wiki"
|
|
26
|
+
Repository = "https://github.com/pquarterman17/quantized"
|
|
27
|
+
Releases = "https://github.com/pquarterman17/quantized/releases"
|
|
28
|
+
Issues = "https://github.com/pquarterman17/quantized/issues"
|
|
29
|
+
|
|
23
30
|
[project.scripts]
|
|
24
31
|
qz = "quantized.cli:main"
|
|
25
32
|
quantized = "quantized.cli:main"
|
|
@@ -0,0 +1,127 @@
|
|
|
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, ``\\AA \\circ \\times \\cdot \\pm \\prime \\,
|
|
17
|
+
\\mathrm \\mathit``, and literal Unicode -- and rejects everything else
|
|
18
|
+
(e.g. ``\\frac``, ``\\sqrt``, ``\\sum``) as invalid markup, rendering it
|
|
19
|
+
literally on screen. Raw matplotlib accepts a much larger command set, so
|
|
20
|
+
without this gate a label like ``$\\frac{1}{2}$`` would render literal text
|
|
21
|
+
("Invalid markup") on screen but an actual fraction in the PDF/SVG export --
|
|
22
|
+
a WYSIWYG violation. `_uses_only_supported_commands` enumerates the SAME
|
|
23
|
+
command set the frontend parser accepts and rejects (falls back to literal)
|
|
24
|
+
any ``$...$`` region using a command outside it, BEFORE the matplotlib trial
|
|
25
|
+
parse below ever runs. Keep `SUPPORTED_MATHTEXT_COMMANDS` in sync with
|
|
26
|
+
richtext.ts's ``GREEK``/``SYMBOLS`` tables + ``mathrm``/``mathit`` handling.
|
|
27
|
+
|
|
28
|
+
Pure layer: string in -> string out. matplotlib is imported lazily (same
|
|
29
|
+
convention as the figure modules -- the heavy import is paid only on export).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import re
|
|
35
|
+
from functools import lru_cache
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
__all__ = ["safe_mathtext_label", "SUPPORTED_MATHTEXT_COMMANDS"]
|
|
39
|
+
|
|
40
|
+
# A "$" not preceded by a backslash -- matplotlib's own math-region rule
|
|
41
|
+
# (matplotlib.cbook.is_math_text counts these; an odd count means the string
|
|
42
|
+
# is rendered as literal text, dollars visible, no mathtext at all).
|
|
43
|
+
_UNESCAPED_DOLLAR = re.compile(r"(?<!\\)\$")
|
|
44
|
+
|
|
45
|
+
# A backslash command name (`\foo`) inside a math region. `\,` (thin space)
|
|
46
|
+
# is handled separately by richtext.ts before its name-based dispatch and
|
|
47
|
+
# has no letters, so it never matches this and is implicitly always allowed.
|
|
48
|
+
_COMMAND = re.compile(r"\\([a-zA-Z]+)")
|
|
49
|
+
|
|
50
|
+
# Mirrors frontend/src/lib/richtext.ts's `GREEK` table: lowercase (+ \var*
|
|
51
|
+
# variants) render italic, uppercase upright -- irrelevant here, we only
|
|
52
|
+
# need the NAME set, not the styling.
|
|
53
|
+
_GREEK_COMMANDS = frozenset(
|
|
54
|
+
{
|
|
55
|
+
"alpha", "beta", "gamma", "delta", "epsilon", "varepsilon", "zeta",
|
|
56
|
+
"eta", "theta", "vartheta", "iota", "kappa", "lambda", "mu", "nu",
|
|
57
|
+
"xi", "pi", "rho", "sigma", "varsigma", "tau", "upsilon", "phi",
|
|
58
|
+
"varphi", "chi", "psi", "omega",
|
|
59
|
+
"Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon",
|
|
60
|
+
"Phi", "Psi", "Omega",
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
# Mirrors richtext.ts's `SYMBOLS` table (non-Greek symbol commands).
|
|
64
|
+
_SYMBOL_COMMANDS = frozenset({"AA", "circ", "times", "cdot", "pm", "prime"})
|
|
65
|
+
# Mirrors richtext.ts's `\mathrm{...}` / `\mathit{...}` style-group handling.
|
|
66
|
+
_STYLE_COMMANDS = frozenset({"mathrm", "mathit"})
|
|
67
|
+
|
|
68
|
+
SUPPORTED_MATHTEXT_COMMANDS = _GREEK_COMMANDS | _SYMBOL_COMMANDS | _STYLE_COMMANDS
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _uses_only_supported_commands(label: str) -> bool:
|
|
72
|
+
"""True when every backslash command inside every ``$...$`` region of
|
|
73
|
+
`label` is in `SUPPORTED_MATHTEXT_COMMANDS`. Text OUTSIDE `$...$` is
|
|
74
|
+
never scanned -- a stray backslash there is the caller's problem, not
|
|
75
|
+
this gate's (and richtext.ts itself only ever considers commands inside
|
|
76
|
+
math regions). Assumes a balanced (even) unescaped-`$` count, as
|
|
77
|
+
guaranteed by `safe_mathtext_label` calling this only after that check.
|
|
78
|
+
"""
|
|
79
|
+
segments = _UNESCAPED_DOLLAR.split(label)
|
|
80
|
+
for i, segment in enumerate(segments):
|
|
81
|
+
if i % 2 == 0:
|
|
82
|
+
continue # literal text outside $...$
|
|
83
|
+
for m in _COMMAND.finditer(segment):
|
|
84
|
+
if m.group(1) not in SUPPORTED_MATHTEXT_COMMANDS:
|
|
85
|
+
return False
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@lru_cache(maxsize=1)
|
|
90
|
+
def _parser() -> Any:
|
|
91
|
+
"""The mathtext trial parser (cached -- construction is not free)."""
|
|
92
|
+
from matplotlib.mathtext import MathTextParser
|
|
93
|
+
|
|
94
|
+
return MathTextParser("agg")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def safe_mathtext_label(label: str) -> str:
|
|
98
|
+
"""Return ``label`` unchanged when it renders safely; else de-math it.
|
|
99
|
+
|
|
100
|
+
- No ``$`` at all: fast path, returned as-is (byte-identical to today).
|
|
101
|
+
- Odd count of unescaped ``$``: matplotlib already treats the whole
|
|
102
|
+
string as literal text -- returned as-is.
|
|
103
|
+
- Balanced ``$...$`` regions using only commands the frontend's
|
|
104
|
+
richtext.ts subset also accepts (`SUPPORTED_MATHTEXT_COMMANDS`), AND
|
|
105
|
+
that trial-parse successfully with matplotlib's mathtext parser ->
|
|
106
|
+
unchanged (mathtext renders it at export, matching the screen).
|
|
107
|
+
Otherwise (an out-of-subset command, e.g. ``\\frac``/``\\sqrt``/
|
|
108
|
+
``\\sum``, OR a parse failure even within the subset, e.g. unbalanced
|
|
109
|
+
braces) -> every unescaped ``$`` is escaped to ``\\$`` so matplotlib
|
|
110
|
+
renders the literal string (dollars visible) instead of raising at
|
|
111
|
+
savefig time OR silently rendering richer math than the screen ever
|
|
112
|
+
showed.
|
|
113
|
+
"""
|
|
114
|
+
if not label or "$" not in label:
|
|
115
|
+
return label
|
|
116
|
+
n_math = len(_UNESCAPED_DOLLAR.findall(label))
|
|
117
|
+
if n_math == 0 or n_math % 2 == 1:
|
|
118
|
+
# Zero unescaped $ (all \$-escaped) or an odd count: matplotlib
|
|
119
|
+
# already renders the whole string as literal text -- leave it be.
|
|
120
|
+
return label
|
|
121
|
+
if not _uses_only_supported_commands(label):
|
|
122
|
+
return _UNESCAPED_DOLLAR.sub(r"\\$", label)
|
|
123
|
+
try:
|
|
124
|
+
_parser().parse(label)
|
|
125
|
+
except Exception: # ANY parse failure means "render literal", never raise
|
|
126
|
+
return _UNESCAPED_DOLLAR.sub(r"\\$", label)
|
|
127
|
+
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(
|
|
@@ -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"))
|
|
@@ -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)
|