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.
Files changed (240) hide show
  1. quantized_lab-0.9.0/PKG-INFO +176 -0
  2. quantized_lab-0.9.0/README.md +137 -0
  3. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/pyproject.toml +9 -2
  4. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/__init__.py +1 -1
  5. quantized_lab-0.9.0/src/quantized/calc/figure_labels.py +127 -0
  6. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_overrides.py +24 -0
  7. quantized_lab-0.9.0/src/quantized/calc/figure_shapes.py +153 -0
  8. quantized_lab-0.9.0/src/quantized/web/assets/index-D8RNGjHO.js +27 -0
  9. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/index.html +1 -1
  10. quantized_lab-0.8.0/PKG-INFO +0 -122
  11. quantized_lab-0.8.0/README.md +0 -88
  12. quantized_lab-0.8.0/src/quantized/calc/figure_labels.py +0 -62
  13. quantized_lab-0.8.0/src/quantized/web/assets/index-BHmmCL-x.js +0 -27
  14. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/.gitignore +0 -0
  15. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/LICENSE +0 -0
  16. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/NOTICE +0 -0
  17. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/__main__.py +0 -0
  18. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/api.py +0 -0
  19. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/app.py +0 -0
  20. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/__init__.py +0 -0
  21. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/_clipfit.py +0 -0
  22. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/_natural_neighbor.py +0 -0
  23. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/aggregate.py +0 -0
  24. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/backgrounds.py +0 -0
  25. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/baseline.py +0 -0
  26. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/batch_fit.py +0 -0
  27. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/constants.py +0 -0
  28. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/corrections.py +0 -0
  29. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/crystallography.py +0 -0
  30. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/diffusion.py +0 -0
  31. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/electrical.py +0 -0
  32. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/electrochemistry.py +0 -0
  33. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/element_data.json +0 -0
  34. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/element_data.py +0 -0
  35. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/errors.py +0 -0
  36. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure.py +0 -0
  37. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_break.py +0 -0
  38. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_categorical.py +0 -0
  39. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_corner.py +0 -0
  40. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_facets.py +0 -0
  41. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_field.py +0 -0
  42. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_hitmap.py +0 -0
  43. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_map.py +0 -0
  44. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_page.py +0 -0
  45. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_scale.py +0 -0
  46. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_statplots.py +0 -0
  47. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_styles.py +0 -0
  48. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_ternary.py +0 -0
  49. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/figure_ticks.py +0 -0
  50. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_autoguess.py +0 -0
  51. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_bootstrap.py +0 -0
  52. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_bumps.py +0 -0
  53. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_constraints.py +0 -0
  54. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_equation.py +0 -0
  55. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_findxy.py +0 -0
  56. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_models.py +0 -0
  57. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_models_special.py +0 -0
  58. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_odr.py +0 -0
  59. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_scan.py +0 -0
  60. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fit_stats.py +0 -0
  61. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/fitting.py +0 -0
  62. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/formula.py +0 -0
  63. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/global_curve_fit.py +0 -0
  64. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/global_fit.py +0 -0
  65. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/interp2d.py +0 -0
  66. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/linecut.py +0 -0
  67. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/magnetic.py +0 -0
  68. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/magnetometry.py +0 -0
  69. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/map.py +0 -0
  70. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/mcmc.py +0 -0
  71. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/optics.py +0 -0
  72. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/pawley.py +0 -0
  73. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_batch.py +0 -0
  74. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_fit.py +0 -0
  75. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_integrate.py +0 -0
  76. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_multifit.py +0 -0
  77. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peak_track.py +0 -0
  78. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peaks.py +0 -0
  79. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/peakshapes.py +0 -0
  80. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/plotting.py +0 -0
  81. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/processing.py +0 -0
  82. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/qspace.py +0 -0
  83. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/reductions.py +0 -0
  84. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/reductions_fft.py +0 -0
  85. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/refl_sld_presets.json +0 -0
  86. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/reflectivity.py +0 -0
  87. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/registry.py +0 -0
  88. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/relaxation.py +0 -0
  89. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/report.py +0 -0
  90. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/report_emit.py +0 -0
  91. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/resample.py +0 -0
  92. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/rsm.py +0 -0
  93. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/rsm_analyze.py +0 -0
  94. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/semiconductor.py +0 -0
  95. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/sld.py +0 -0
  96. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/sld_formula.py +0 -0
  97. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/spectral.py +0 -0
  98. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/statplots.py +0 -0
  99. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats.py +0 -0
  100. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_anova2.py +0 -0
  101. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_anova_ext.py +0 -0
  102. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_dist.py +0 -0
  103. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_glm.py +0 -0
  104. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_multivar.py +0 -0
  105. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_roc.py +0 -0
  106. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_survival.py +0 -0
  107. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/stats_tests.py +0 -0
  108. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/substrates.py +0 -0
  109. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/superconductor.py +0 -0
  110. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/surface_fit.py +0 -0
  111. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/surface_models.py +0 -0
  112. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/thermal.py +0 -0
  113. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/thin_film.py +0 -0
  114. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/unit_convert.py +0 -0
  115. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/units.py +0 -0
  116. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/vacuum.py +0 -0
  117. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/calc/xray.py +0 -0
  118. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/cli.py +0 -0
  119. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/datastruct.py +0 -0
  120. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/__init__.py +0 -0
  121. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/_hdf5_layout.py +0 -0
  122. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/_jcamp_asdf.py +0 -0
  123. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/_xrdml_scan.py +0 -0
  124. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/base.py +0 -0
  125. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/bruker_brml.py +0 -0
  126. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/bruker_raw.py +0 -0
  127. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/cif.py +0 -0
  128. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/consolidated.py +0 -0
  129. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/delimited.py +0 -0
  130. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/excel.py +0 -0
  131. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/hdf5.py +0 -0
  132. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/import_filters.py +0 -0
  133. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/import_preview.py +0 -0
  134. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/jcamp.py +0 -0
  135. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/lakeshore.py +0 -0
  136. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/ncnr.py +0 -0
  137. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/netcdf.py +0 -0
  138. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/opus.py +0 -0
  139. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin.py +0 -0
  140. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_com.py +0 -0
  141. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/__init__.py +0 -0
  142. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/annotation_marks.py +0 -0
  143. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/container.py +0 -0
  144. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/curve_style_color.py +0 -0
  145. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figure_geometry.py +0 -0
  146. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figure_layers.py +0 -0
  147. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figure_text.py +0 -0
  148. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figures.py +0 -0
  149. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/figures_opju.py +0 -0
  150. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/notes.py +0 -0
  151. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opj.py +0 -0
  152. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opj_curves.py +0 -0
  153. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opj_shapes.py +0 -0
  154. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju.py +0 -0
  155. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_axis_real_form.py +0 -0
  156. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_axis_specimen_form.py +0 -0
  157. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_codec.py +0 -0
  158. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_curves.py +0 -0
  159. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_curves_allcols.py +0 -0
  160. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_figure_curves.py +0 -0
  161. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_figure_text.py +0 -0
  162. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/opju_reports.py +0 -0
  163. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/origin_richtext.py +0 -0
  164. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/preview.py +0 -0
  165. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/templates.py +0 -0
  166. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/tree.py +0 -0
  167. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/tree_opju.py +0 -0
  168. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/windows.py +0 -0
  169. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/windows_opju.py +0 -0
  170. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/writer.py +0 -0
  171. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/origin_project/writer_blocks.py +0 -0
  172. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/qd.py +0 -0
  173. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/refl1d.py +0 -0
  174. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/registry.py +0 -0
  175. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/report_export.py +0 -0
  176. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/rigaku.py +0 -0
  177. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/sims.py +0 -0
  178. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/spc.py +0 -0
  179. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/xrd_csv.py +0 -0
  180. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/io/xrdml.py +0 -0
  181. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/jobs.py +0 -0
  182. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/__init__.py +0 -0
  183. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/contract.py +0 -0
  184. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/loader.py +0 -0
  185. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/plugins/steps.py +0 -0
  186. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/__init__.py +0 -0
  187. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_bookcache.py +0 -0
  188. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_export_common.py +0 -0
  189. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_payload.py +0 -0
  190. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/_uploadcache.py +0 -0
  191. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/aggregate.py +0 -0
  192. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/baseline.py +0 -0
  193. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/books.py +0 -0
  194. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/calc.py +0 -0
  195. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/corrections.py +0 -0
  196. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/crystallography.py +0 -0
  197. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/diffusion.py +0 -0
  198. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/electrical.py +0 -0
  199. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/electrochemistry.py +0 -0
  200. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export.py +0 -0
  201. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export_facets.py +0 -0
  202. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export_figures.py +0 -0
  203. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/export_page.py +0 -0
  204. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/fitting.py +0 -0
  205. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/fitting_bumps.py +0 -0
  206. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/import_template.py +0 -0
  207. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/import_wizard.py +0 -0
  208. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/jobs_api.py +0 -0
  209. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/magnetic.py +0 -0
  210. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/magnetometry.py +0 -0
  211. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/optics.py +0 -0
  212. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/parsers.py +0 -0
  213. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/peaks.py +0 -0
  214. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/plot.py +0 -0
  215. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/reductions.py +0 -0
  216. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/reference.py +0 -0
  217. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/reflectivity.py +0 -0
  218. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/report_export.py +0 -0
  219. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/rsm.py +0 -0
  220. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/samples.py +0 -0
  221. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/semiconductor.py +0 -0
  222. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/sld.py +0 -0
  223. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/spectral.py +0 -0
  224. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/statplots.py +0 -0
  225. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/stats.py +0 -0
  226. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/stats_design.py +0 -0
  227. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/substrates.py +0 -0
  228. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/superconductor.py +0 -0
  229. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/thermal.py +0 -0
  230. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/thin_film.py +0 -0
  231. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/vacuum.py +0 -0
  232. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/routes/xray.py +0 -0
  233. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/samples/demo_vsm.csv +0 -0
  234. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/server_launch.py +0 -0
  235. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/assets/JetBrainsMono-Bold-CUogYd9I.woff2 +0 -0
  236. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/assets/JetBrainsMono-Regular-CA-Os4ii.woff2 +0 -0
  237. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/assets/index-BiZzN7J6.css +0 -0
  238. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/src/quantized/web/loading.html +0 -0
  239. {quantized_lab-0.8.0 → quantized_lab-0.9.0}/tools/origin_compare/README.md +0 -0
  240. {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.8.0"
4
- description = "Clean-architecture Python + React port of the quantized_matlab scientific toolbox (magnetometry / XRD / lab data)."
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"
@@ -7,5 +7,5 @@ Layered, enforced architecture (see CLAUDE.md):
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- __version__ = "0.7.0"
10
+ __version__ = "0.9.0"
11
11
  __all__ = ["__version__"]
@@ -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)