scitex 2.14.0__py3-none-any.whl
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.
- scitex/.mcp.json +46 -0
- scitex/__init__.py +268 -0
- scitex/__main__.py +45 -0
- scitex/__version__.py +16 -0
- scitex/_install_guide.py +262 -0
- scitex/_mcp_tools/__init__.py +36 -0
- scitex/_mcp_tools/audio.py +134 -0
- scitex/_mcp_tools/canvas.py +128 -0
- scitex/_mcp_tools/capture.py +174 -0
- scitex/_mcp_tools/diagram.py +106 -0
- scitex/_mcp_tools/plt.py +350 -0
- scitex/_mcp_tools/scholar.py +373 -0
- scitex/_mcp_tools/stats.py +206 -0
- scitex/_mcp_tools/template.py +66 -0
- scitex/_mcp_tools/ui.py +80 -0
- scitex/_mcp_tools/writer.py +220 -0
- scitex/_optional_deps.py +333 -0
- scitex/ai/README.md +295 -0
- scitex/ai/__init__.py +74 -0
- scitex/ai/_gen_ai/_Anthropic.py +173 -0
- scitex/ai/_gen_ai/_BaseGenAI.py +337 -0
- scitex/ai/_gen_ai/_DeepSeek.py +183 -0
- scitex/ai/_gen_ai/_Google.py +162 -0
- scitex/ai/_gen_ai/_Groq.py +95 -0
- scitex/ai/_gen_ai/_Llama.py +144 -0
- scitex/ai/_gen_ai/_OpenAI.py +235 -0
- scitex/ai/_gen_ai/_PARAMS.py +552 -0
- scitex/ai/_gen_ai/_Perplexity.py +207 -0
- scitex/ai/_gen_ai/__init__.py +44 -0
- scitex/ai/_gen_ai/_calc_cost.py +78 -0
- scitex/ai/_gen_ai/_format_output_func.py +185 -0
- scitex/ai/_gen_ai/_genai_factory.py +71 -0
- scitex/ai/activation/__init__.py +8 -0
- scitex/ai/activation/_define.py +11 -0
- scitex/ai/classification/Classifier.py +131 -0
- scitex/ai/classification/CrossValidationExperiment.py +368 -0
- scitex/ai/classification/README.md +251 -0
- scitex/ai/classification/__init__.py +46 -0
- scitex/ai/classification/examples/timeseries_cv_demo.py +416 -0
- scitex/ai/classification/examples/verify_multi/config.json +13 -0
- scitex/ai/classification/examples/verify_multi/multi_task_comparison.md +20 -0
- scitex/ai/classification/examples/verify_multi/multi_task_validation.json +103 -0
- scitex/ai/classification/examples/verify_multi/paper_export/README.md +20 -0
- scitex/ai/classification/examples/verify_multi/paper_export/raw_results.json +174 -0
- scitex/ai/classification/examples/verify_multi/task1/metadata.json +56 -0
- scitex/ai/classification/examples/verify_multi/task1/paper_export/README.md +20 -0
- scitex/ai/classification/examples/verify_multi/task1/paper_export/raw_results.json +93 -0
- scitex/ai/classification/examples/verify_multi/task1/paper_export/summary_table.tex +15 -0
- scitex/ai/classification/examples/verify_multi/task1/report.md +31 -0
- scitex/ai/classification/examples/verify_multi/task1/validation_report.json +47 -0
- scitex/ai/classification/examples/verify_multi/task2/metadata.json +56 -0
- scitex/ai/classification/examples/verify_multi/task2/paper_export/README.md +20 -0
- scitex/ai/classification/examples/verify_multi/task2/paper_export/raw_results.json +93 -0
- scitex/ai/classification/examples/verify_multi/task2/paper_export/summary_table.tex +15 -0
- scitex/ai/classification/examples/verify_multi/task2/report.md +31 -0
- scitex/ai/classification/examples/verify_multi/task2/validation_report.json +47 -0
- scitex/ai/classification/examples/verify_test/metadata.json +62 -0
- scitex/ai/classification/examples/verify_test/paper_export/README.md +20 -0
- scitex/ai/classification/examples/verify_test/paper_export/raw_results.json +131 -0
- scitex/ai/classification/examples/verify_test/paper_export/summary_table.tex +15 -0
- scitex/ai/classification/examples/verify_test/report.md +31 -0
- scitex/ai/classification/examples/verify_test/validation_report.json +52 -0
- scitex/ai/classification/reporters/_BaseClassificationReporter.py +283 -0
- scitex/ai/classification/reporters/_ClassificationReporter.py +758 -0
- scitex/ai/classification/reporters/_MultiClassificationReporter.py +403 -0
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +1778 -0
- scitex/ai/classification/reporters/__init__.py +11 -0
- scitex/ai/classification/reporters/reporter_utils/_Plotter.py +1122 -0
- scitex/ai/classification/reporters/reporter_utils/__init__.py +72 -0
- scitex/ai/classification/reporters/reporter_utils/aggregation.py +439 -0
- scitex/ai/classification/reporters/reporter_utils/data_models.py +321 -0
- scitex/ai/classification/reporters/reporter_utils/reporting.py +1223 -0
- scitex/ai/classification/reporters/reporter_utils/storage.py +224 -0
- scitex/ai/classification/reporters/reporter_utils/validation.py +382 -0
- scitex/ai/classification/timeseries/README.md +313 -0
- scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +643 -0
- scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +732 -0
- scitex/ai/classification/timeseries/_TimeSeriesMetadata.py +139 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +1640 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +1609 -0
- scitex/ai/classification/timeseries/_TimeSeriesStrategy.py +83 -0
- scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +697 -0
- scitex/ai/classification/timeseries/__init__.py +37 -0
- scitex/ai/classification/timeseries/_normalize_timestamp.py +439 -0
- scitex/ai/classification/timeseries/run_all.sh +30 -0
- scitex/ai/clustering/__init__.py +11 -0
- scitex/ai/clustering/_pca.py +114 -0
- scitex/ai/clustering/_umap.py +375 -0
- scitex/ai/feature_extraction/__init__.py +58 -0
- scitex/ai/feature_extraction/vit.py +148 -0
- scitex/ai/feature_selection/__init__.py +30 -0
- scitex/ai/feature_selection/feature_selection.py +359 -0
- scitex/ai/loss/_L1L2Losses.py +34 -0
- scitex/ai/loss/__init__.py +12 -0
- scitex/ai/loss/multi_task_loss.py +47 -0
- scitex/ai/metrics/__init__.py +56 -0
- scitex/ai/metrics/_calc_bacc.py +61 -0
- scitex/ai/metrics/_calc_bacc_from_conf_mat.py +38 -0
- scitex/ai/metrics/_calc_clf_report.py +78 -0
- scitex/ai/metrics/_calc_conf_mat.py +95 -0
- scitex/ai/metrics/_calc_feature_importance.py +179 -0
- scitex/ai/metrics/_calc_mcc.py +61 -0
- scitex/ai/metrics/_calc_pre_rec_auc.py +116 -0
- scitex/ai/metrics/_calc_roc_auc.py +112 -0
- scitex/ai/metrics/_calc_seizure_prediction_metrics.py +505 -0
- scitex/ai/metrics/_calc_silhouette_score.py +501 -0
- scitex/ai/metrics/_normalize_labels.py +83 -0
- scitex/ai/optim/MIGRATION.md +43 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/LICENSE +201 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/README.md +80 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/__init__.py +0 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/__init__.py +3 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger.py +204 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger2020.py +235 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger913A.py +212 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/rangerqh.py +184 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger-init.jpg +0 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger-with-gc-options.jpg +0 -0
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/setup.py +24 -0
- scitex/ai/optim/__init__.py +13 -0
- scitex/ai/optim/_get_set.py +31 -0
- scitex/ai/optim/_optimizers.py +71 -0
- scitex/ai/plt/__init__.py +65 -0
- scitex/ai/plt/_plot_feature_importance.py +321 -0
- scitex/ai/plt/_plot_learning_curve.py +330 -0
- scitex/ai/plt/_plot_optuna_study.py +226 -0
- scitex/ai/plt/_plot_pre_rec_curve.py +284 -0
- scitex/ai/plt/_plot_roc_curve.py +253 -0
- scitex/ai/plt/_stx_conf_mat.py +662 -0
- scitex/ai/sampling/undersample.py +44 -0
- scitex/ai/sk/__init__.py +11 -0
- scitex/ai/sk/_clf.py +58 -0
- scitex/ai/sk/_to_sktime.py +100 -0
- scitex/ai/sklearn/__init__.py +26 -0
- scitex/ai/sklearn/clf.py +58 -0
- scitex/ai/sklearn/to_sktime.py +100 -0
- scitex/ai/training/_EarlyStopping.py +149 -0
- scitex/ai/training/_LearningCurveLogger.py +552 -0
- scitex/ai/training/__init__.py +7 -0
- scitex/ai/utils/__init__.py +22 -0
- scitex/ai/utils/_check_params.py +49 -0
- scitex/ai/utils/_default_dataset.py +46 -0
- scitex/ai/utils/_format_samples_for_sktime.py +26 -0
- scitex/ai/utils/_label_encoder.py +134 -0
- scitex/ai/utils/_merge_labels.py +22 -0
- scitex/ai/utils/_sliding_window_data_augmentation.py +11 -0
- scitex/ai/utils/_under_sample.py +51 -0
- scitex/ai/utils/_verify_n_gpus.py +16 -0
- scitex/ai/utils/grid_search.py +148 -0
- scitex/audio/README.md +129 -0
- scitex/audio/__init__.py +392 -0
- scitex/audio/__main__.py +125 -0
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp/__init__.py +4 -0
- scitex/audio/_mcp/handlers.py +366 -0
- scitex/audio/_mcp/tool_schemas.py +203 -0
- scitex/audio/_tts.py +334 -0
- scitex/audio/engines/__init__.py +44 -0
- scitex/audio/engines/base.py +275 -0
- scitex/audio/engines/elevenlabs_engine.py +146 -0
- scitex/audio/engines/gtts_engine.py +162 -0
- scitex/audio/engines/pyttsx3_engine.py +131 -0
- scitex/audio/mcp_server.py +293 -0
- scitex/benchmark/__init__.py +42 -0
- scitex/benchmark/benchmark.py +407 -0
- scitex/benchmark/monitor.py +380 -0
- scitex/benchmark/profiler.py +300 -0
- scitex/bridge/__init__.py +121 -0
- scitex/bridge/_figrecipe.py +277 -0
- scitex/bridge/_helpers.py +150 -0
- scitex/bridge/_plt_vis.py +542 -0
- scitex/bridge/_protocol.py +283 -0
- scitex/bridge/_stats_plt.py +272 -0
- scitex/bridge/_stats_vis.py +281 -0
- scitex/browser/README.md +167 -0
- scitex/browser/__init__.py +139 -0
- scitex/browser/auth/__init__.py +35 -0
- scitex/browser/auth/google.py +386 -0
- scitex/browser/automation/CookieHandler.py +215 -0
- scitex/browser/automation/__init__.py +11 -0
- scitex/browser/collaboration/__init__.py +62 -0
- scitex/browser/collaboration/auth_helpers.py +96 -0
- scitex/browser/collaboration/collaborative_agent.py +138 -0
- scitex/browser/collaboration/credential_manager.py +188 -0
- scitex/browser/collaboration/interactive_panel.py +400 -0
- scitex/browser/collaboration/persistent_browser.py +170 -0
- scitex/browser/collaboration/shared_session.py +392 -0
- scitex/browser/collaboration/standard_interactions.py +247 -0
- scitex/browser/collaboration/visual_feedback.py +181 -0
- scitex/browser/core/BrowserMixin.py +321 -0
- scitex/browser/core/ChromeProfileManager.py +438 -0
- scitex/browser/core/__init__.py +18 -0
- scitex/browser/debugging/__init__.py +74 -0
- scitex/browser/debugging/_browser_logger.py +644 -0
- scitex/browser/debugging/_failure_capture.py +379 -0
- scitex/browser/debugging/_highlight_element.py +152 -0
- scitex/browser/debugging/_show_grid.py +153 -0
- scitex/browser/debugging/_sync_session.py +260 -0
- scitex/browser/debugging/_test_monitor.py +293 -0
- scitex/browser/debugging/_visual_cursor.py +443 -0
- scitex/browser/docs/ABOUT_PLAYWRIGHT.md +49 -0
- scitex/browser/interaction/__init__.py +24 -0
- scitex/browser/interaction/click_center.py +150 -0
- scitex/browser/interaction/click_with_fallbacks.py +203 -0
- scitex/browser/interaction/close_popups.py +511 -0
- scitex/browser/interaction/fill_with_fallbacks.py +209 -0
- scitex/browser/pdf/__init__.py +16 -0
- scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +201 -0
- scitex/browser/pdf/detect_chrome_pdf_viewer.py +195 -0
- scitex/browser/remote/CaptchaHandler.py +447 -0
- scitex/browser/remote/ZenRowsAPIClient.py +341 -0
- scitex/browser/remote/ZenRowsBrowserManager.py +596 -0
- scitex/browser/remote/__init__.py +11 -0
- scitex/browser/stealth/HumanBehavior.py +339 -0
- scitex/browser/stealth/StealthManager.py +993 -0
- scitex/browser/stealth/__init__.py +9 -0
- scitex/canvas/README.md +386 -0
- scitex/canvas/__init__.py +340 -0
- scitex/canvas/_mcp/__init__.py +4 -0
- scitex/canvas/_mcp/handlers.py +372 -0
- scitex/canvas/_mcp/tool_schemas.py +219 -0
- scitex/canvas/backend/__init__.py +56 -0
- scitex/canvas/backend/_export.py +166 -0
- scitex/canvas/backend/_parser.py +188 -0
- scitex/canvas/backend/_render.py +385 -0
- scitex/canvas/canvas.py +434 -0
- scitex/canvas/docs/CANVAS_ARCHITECTURE.md +307 -0
- scitex/canvas/editor/__init__.py +26 -0
- scitex/canvas/editor/_dearpygui_editor.py +1976 -0
- scitex/canvas/editor/_defaults.py +300 -0
- scitex/canvas/editor/_flask_editor.py +37 -0
- scitex/canvas/editor/_mpl_editor.py +246 -0
- scitex/canvas/editor/_qt_editor.py +1096 -0
- scitex/canvas/editor/_tkinter_editor.py +523 -0
- scitex/canvas/editor/edit/__init__.py +47 -0
- scitex/canvas/editor/edit/backend_detector.py +109 -0
- scitex/canvas/editor/edit/bundle_resolver.py +248 -0
- scitex/canvas/editor/edit/editor_launcher.py +292 -0
- scitex/canvas/editor/edit/manual_handler.py +53 -0
- scitex/canvas/editor/edit/panel_loader.py +246 -0
- scitex/canvas/editor/edit/path_resolver.py +67 -0
- scitex/canvas/editor/flask_editor/__init__.py +21 -0
- scitex/canvas/editor/flask_editor/_bbox.py +1340 -0
- scitex/canvas/editor/flask_editor/_core.py +1688 -0
- scitex/canvas/editor/flask_editor/_plotter.py +601 -0
- scitex/canvas/editor/flask_editor/_renderer.py +854 -0
- scitex/canvas/editor/flask_editor/_utils.py +80 -0
- scitex/canvas/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/canvas/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/canvas/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/canvas/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/canvas/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/canvas/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/canvas/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/canvas/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/canvas/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/canvas/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/canvas/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/canvas/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/canvas/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/canvas/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/canvas/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/canvas/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/canvas/editor/flask_editor/static/css/index.css +31 -0
- scitex/canvas/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/canvas/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/canvas/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/canvas/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/canvas/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/canvas/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/canvas/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/canvas/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/canvas/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/canvas/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/canvas/editor/flask_editor/static/js/core/api.js +287 -0
- scitex/canvas/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/canvas/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/canvas/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/canvas/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/canvas/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/canvas/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/canvas/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/canvas/editor/flask_editor/static/js/main.js +426 -0
- scitex/canvas/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/canvas/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/canvas/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/canvas/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/canvas/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/canvas/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/canvas/editor/flask_editor/templates/__init__.py +123 -0
- scitex/canvas/editor/flask_editor/templates/_html.py +852 -0
- scitex/canvas/editor/flask_editor/templates/_scripts.py +4933 -0
- scitex/canvas/editor/flask_editor/templates/_styles.py +1658 -0
- scitex/canvas/io/__init__.py +105 -0
- scitex/canvas/io/_bundle.py +1121 -0
- scitex/canvas/io/_canvas.py +230 -0
- scitex/canvas/io/_data.py +208 -0
- scitex/canvas/io/_directory.py +205 -0
- scitex/canvas/io/_export.py +463 -0
- scitex/canvas/io/_load.py +168 -0
- scitex/canvas/io/_panel.py +432 -0
- scitex/canvas/io/_save.py +131 -0
- scitex/canvas/mcp_server.py +151 -0
- scitex/canvas/model/__init__.py +118 -0
- scitex/canvas/model/_annotations.py +116 -0
- scitex/canvas/model/_axes.py +153 -0
- scitex/canvas/model/_figure.py +139 -0
- scitex/canvas/model/_guides.py +105 -0
- scitex/canvas/model/_plot.py +124 -0
- scitex/canvas/model/_plot_types.py +682 -0
- scitex/canvas/model/_styles.py +246 -0
- scitex/canvas/utils/__init__.py +75 -0
- scitex/canvas/utils/_defaults.py +334 -0
- scitex/canvas/utils/_validate.py +198 -0
- scitex/capture/README.md +284 -0
- scitex/capture/TODO.md +40 -0
- scitex/capture/__init__.py +109 -0
- scitex/capture/__main__.py +24 -0
- scitex/capture/_mcp/__init__.py +11 -0
- scitex/capture/_mcp/handlers.py +438 -0
- scitex/capture/_mcp/tool_schemas.py +270 -0
- scitex/capture/capture.py +820 -0
- scitex/capture/cli.py +208 -0
- scitex/capture/gif.py +340 -0
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_server.py +977 -0
- scitex/capture/powershell/capture_all_desktops.ps1 +79 -0
- scitex/capture/powershell/capture_all_monitors.ps1 +63 -0
- scitex/capture/powershell/capture_single_monitor.ps1 +77 -0
- scitex/capture/powershell/capture_url.ps1 +170 -0
- scitex/capture/powershell/capture_window_by_handle.ps1 +129 -0
- scitex/capture/powershell/detect_monitors_and_desktops.ps1 +143 -0
- scitex/capture/powershell/enumerate_virtual_desktops.ps1 +60 -0
- scitex/capture/session.py +71 -0
- scitex/capture/utils.py +670 -0
- scitex/cli/__init__.py +64 -0
- scitex/cli/audio.py +331 -0
- scitex/cli/browser.py +310 -0
- scitex/cli/capture.py +325 -0
- scitex/cli/cloud.py +764 -0
- scitex/cli/config.py +224 -0
- scitex/cli/convert.py +425 -0
- scitex/cli/main.py +235 -0
- scitex/cli/mcp.py +336 -0
- scitex/cli/repro.py +251 -0
- scitex/cli/resource.py +258 -0
- scitex/cli/scholar/__init__.py +89 -0
- scitex/cli/scholar/_fetch.py +347 -0
- scitex/cli/scholar/_jobs.py +306 -0
- scitex/cli/scholar/_library.py +152 -0
- scitex/cli/scholar/_utils.py +35 -0
- scitex/cli/security.py +103 -0
- scitex/cli/stats.py +343 -0
- scitex/cli/template.py +254 -0
- scitex/cli/tex.py +304 -0
- scitex/cli/web.py +405 -0
- scitex/cli/writer.py +329 -0
- scitex/cloud/__init__.py +134 -0
- scitex/cloud/_matplotlib_hook.py +146 -0
- scitex/compat/__init__.py +74 -0
- scitex/config/README.md +313 -0
- scitex/config/_PriorityConfig.py +292 -0
- scitex/config/_ScitexConfig.py +319 -0
- scitex/config/__init__.py +56 -0
- scitex/config/_paths.py +325 -0
- scitex/config/default.yaml +112 -0
- scitex/context/__init__.py +9 -0
- scitex/context/_suppress_output.py +39 -0
- scitex/cv/__init__.py +87 -0
- scitex/cv/_draw.py +236 -0
- scitex/cv/_filters.py +217 -0
- scitex/cv/_io.py +157 -0
- scitex/cv/_transform.py +184 -0
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/README.md +281 -0
- scitex/db/_BaseMixins/_BaseBackupMixin.py +32 -0
- scitex/db/_BaseMixins/_BaseBatchMixin.py +33 -0
- scitex/db/_BaseMixins/_BaseBlobMixin.py +83 -0
- scitex/db/_BaseMixins/_BaseConnectionMixin.py +43 -0
- scitex/db/_BaseMixins/_BaseImportExportMixin.py +37 -0
- scitex/db/_BaseMixins/_BaseIndexMixin.py +31 -0
- scitex/db/_BaseMixins/_BaseMaintenanceMixin.py +31 -0
- scitex/db/_BaseMixins/_BaseQueryMixin.py +54 -0
- scitex/db/_BaseMixins/_BaseRowMixin.py +34 -0
- scitex/db/_BaseMixins/_BaseSchemaMixin.py +44 -0
- scitex/db/_BaseMixins/_BaseTableMixin.py +68 -0
- scitex/db/_BaseMixins/_BaseTransactionMixin.py +50 -0
- scitex/db/_BaseMixins/__init__.py +30 -0
- scitex/db/__init__.py +49 -0
- scitex/db/__main__.py +62 -0
- scitex/db/_check_health.py +365 -0
- scitex/db/_delete_duplicates.py +39 -0
- scitex/db/_inspect.py +368 -0
- scitex/db/_inspect_optimized.py +305 -0
- scitex/db/_postgresql/_PostgreSQL.py +125 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_BackupMixin.py +166 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_BatchMixin.py +82 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_BlobMixin.py +233 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_ConnectionMixin.py +90 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_ImportExportMixin.py +57 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_IndexMixin.py +64 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_MaintenanceMixin.py +172 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_QueryMixin.py +108 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_RowMixin.py +77 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_SchemaMixin.py +126 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_TableMixin.py +176 -0
- scitex/db/_postgresql/_PostgreSQLMixins/_TransactionMixin.py +55 -0
- scitex/db/_postgresql/_PostgreSQLMixins/__init__.py +34 -0
- scitex/db/_postgresql/__init__.py +6 -0
- scitex/db/_sqlite3/README.md +191 -0
- scitex/db/_sqlite3/README_v01.md +126 -0
- scitex/db/_sqlite3/_SQLite3.py +208 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin.py +580 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin_v01-need-_hash-col.py +522 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_BatchMixin.py +245 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_BlobMixin.py +281 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin.py +555 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ConnectionMixin.py +122 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_GitMixin.py +555 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_ImportExportMixin.py +78 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_IndexMixin.py +34 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_MaintenanceMixin.py +177 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_QueryMixin.py +109 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_RowMixin.py +115 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_TableMixin.py +236 -0
- scitex/db/_sqlite3/_SQLite3Mixins/_TransactionMixin.py +71 -0
- scitex/db/_sqlite3/_SQLite3Mixins/__init__.py +43 -0
- scitex/db/_sqlite3/__init__.py +7 -0
- scitex/db/_sqlite3/_delete_duplicates.py +285 -0
- scitex/decorators/BLUEPRINT.md +101 -0
- scitex/decorators/README.md +179 -0
- scitex/decorators/__init__.py +90 -0
- scitex/decorators/_auto_order.py +172 -0
- scitex/decorators/_batch_fn.py +134 -0
- scitex/decorators/_cache_disk.py +39 -0
- scitex/decorators/_cache_disk_async.py +50 -0
- scitex/decorators/_cache_mem.py +12 -0
- scitex/decorators/_combined.py +104 -0
- scitex/decorators/_converters.py +303 -0
- scitex/decorators/_deprecated.py +201 -0
- scitex/decorators/_not_implemented.py +30 -0
- scitex/decorators/_numpy_fn.py +101 -0
- scitex/decorators/_pandas_fn.py +138 -0
- scitex/decorators/_preserve_doc.py +19 -0
- scitex/decorators/_signal_fn.py +111 -0
- scitex/decorators/_timeout.py +55 -0
- scitex/decorators/_torch_fn.py +155 -0
- scitex/decorators/_wrap.py +39 -0
- scitex/decorators/_xarray_fn.py +108 -0
- scitex/dev/__init__.py +20 -0
- scitex/dev/_analyze_code_flow.py +282 -0
- scitex/dev/_pyproject.py +405 -0
- scitex/dev/_reload.py +59 -0
- scitex/dev/cv/__init__.py +60 -0
- scitex/dev/cv/_compose.py +330 -0
- scitex/dev/cv/_title_card.py +351 -0
- scitex/dev/plt/__init__.py +271 -0
- scitex/dev/plt/data/mpl/PLOTTING_FUNCTIONS.yaml +90 -0
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES.yaml +1571 -0
- scitex/dev/plt/data/mpl/PLOTTING_SIGNATURES_DETAILED.yaml +6262 -0
- scitex/dev/plt/data/mpl/SIGNATURES_FLATTENED.yaml +1274 -0
- scitex/dev/plt/data/mpl/dir_ax.txt +459 -0
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dev/plt/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/plot_mpl_bar.py +29 -0
- scitex/dev/plt/plot_mpl_barh.py +29 -0
- scitex/dev/plt/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/plot_mpl_contour.py +31 -0
- scitex/dev/plt/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/plot_mpl_fill.py +30 -0
- scitex/dev/plt/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/plot_mpl_hist.py +28 -0
- scitex/dev/plt/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/plot_mpl_pie.py +29 -0
- scitex/dev/plt/plot_mpl_plot.py +29 -0
- scitex/dev/plt/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/plot_mpl_stem.py +29 -0
- scitex/dev/plt/plot_mpl_step.py +29 -0
- scitex/dev/plt/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/plot_sns_barplot.py +29 -0
- scitex/dev/plt/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/plot_sns_histplot.py +29 -0
- scitex/dev/plt/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/plot_stx_bar.py +29 -0
- scitex/dev/plt/plot_stx_barh.py +29 -0
- scitex/dev/plt/plot_stx_box.py +28 -0
- scitex/dev/plt/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/plot_stx_contour.py +31 -0
- scitex/dev/plt/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/plot_stx_fillv.py +28 -0
- scitex/dev/plt/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/plot_stx_image.py +28 -0
- scitex/dev/plt/plot_stx_imshow.py +28 -0
- scitex/dev/plt/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/plot_stx_kde.py +28 -0
- scitex/dev/plt/plot_stx_line.py +28 -0
- scitex/dev/plt/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/plot_stx_raster.py +28 -0
- scitex/dev/plt/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/plot_stx_scatter.py +29 -0
- scitex/dev/plt/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/plot_stx_violin.py +28 -0
- scitex/dev/plt/plot_stx_violinplot.py +28 -0
- scitex/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_mcp/__init__.py +4 -0
- scitex/diagram/_mcp/handlers.py +400 -0
- scitex/diagram/_mcp/tool_schemas.py +157 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/diagram/mcp_server.py +151 -0
- scitex/dict/_DotDict.py +423 -0
- scitex/dict/__init__.py +21 -0
- scitex/dict/_flatten.py +20 -0
- scitex/dict/_listed_dict.py +42 -0
- scitex/dict/_pop_keys.py +30 -0
- scitex/dict/_replace.py +12 -0
- scitex/dict/_safe_merge.py +53 -0
- scitex/dict/_to_str.py +34 -0
- scitex/dsp/README.md +147 -0
- scitex/dsp/__init__.py +86 -0
- scitex/dsp/_crop.py +124 -0
- scitex/dsp/_demo_sig.py +377 -0
- scitex/dsp/_detect_ripples.py +214 -0
- scitex/dsp/_ensure_3d.py +18 -0
- scitex/dsp/_hilbert.py +78 -0
- scitex/dsp/_listen.py +702 -0
- scitex/dsp/_misc.py +30 -0
- scitex/dsp/_mne.py +43 -0
- scitex/dsp/_modulation_index.py +93 -0
- scitex/dsp/_pac.py +337 -0
- scitex/dsp/_psd.py +114 -0
- scitex/dsp/_resample.py +86 -0
- scitex/dsp/_time.py +39 -0
- scitex/dsp/_transform.py +81 -0
- scitex/dsp/_wavelet.py +212 -0
- scitex/dsp/add_noise.py +128 -0
- scitex/dsp/audio.md +22 -0
- scitex/dsp/example.py +262 -0
- scitex/dsp/filt.py +165 -0
- scitex/dsp/norm.py +33 -0
- scitex/dsp/params.py +51 -0
- scitex/dsp/reference.py +60 -0
- scitex/dsp/template.py +26 -0
- scitex/dsp/utils/__init__.py +19 -0
- scitex/dsp/utils/_differential_bandpass_filters.py +148 -0
- scitex/dsp/utils/_ensure_3d.py +18 -0
- scitex/dsp/utils/_ensure_even_len.py +10 -0
- scitex/dsp/utils/_zero_pad.py +62 -0
- scitex/dsp/utils/filter.py +408 -0
- scitex/dsp/utils/pac.py +177 -0
- scitex/dt/__init__.py +45 -0
- scitex/dt/_linspace.py +130 -0
- scitex/dt/_normalize_timestamp.py +435 -0
- scitex/errors.py +130 -0
- scitex/etc/__init__.py +15 -0
- scitex/etc/wait_key.py +34 -0
- scitex/gen/README.md +71 -0
- scitex/gen/_DimHandler.py +196 -0
- scitex/gen/_TimeStamper.py +244 -0
- scitex/gen/__init__.py +170 -0
- scitex/gen/_alternate_kwarg.py +13 -0
- scitex/gen/_cache.py +11 -0
- scitex/gen/_check_host.py +34 -0
- scitex/gen/_ci.py +12 -0
- scitex/gen/_deprecated_close.py +81 -0
- scitex/gen/_deprecated_start.py +28 -0
- scitex/gen/_detect_environment.py +155 -0
- scitex/gen/_detect_notebook_path.py +173 -0
- scitex/gen/_embed.py +82 -0
- scitex/gen/_get_notebook_path.py +276 -0
- scitex/gen/_inspect_module.py +256 -0
- scitex/gen/_is_ipython.py +12 -0
- scitex/gen/_less.py +48 -0
- scitex/gen/_list_packages.py +139 -0
- scitex/gen/_mat2py.py +88 -0
- scitex/gen/_norm.py +214 -0
- scitex/gen/_norm_cache.py +282 -0
- scitex/gen/_paste.py +18 -0
- scitex/gen/_print_config.py +81 -0
- scitex/gen/_shell.py +48 -0
- scitex/gen/_src.py +108 -0
- scitex/gen/_symlink.py +55 -0
- scitex/gen/_symlog.py +27 -0
- scitex/gen/_title2path.py +60 -0
- scitex/gen/_title_case.py +89 -0
- scitex/gen/_to_even.py +84 -0
- scitex/gen/_to_odd.py +34 -0
- scitex/gen/_to_rank.py +39 -0
- scitex/gen/_transpose.py +37 -0
- scitex/gen/_type.py +78 -0
- scitex/gen/_var_info.py +73 -0
- scitex/gen/_wrap.py +17 -0
- scitex/gen/_xml2dict.py +76 -0
- scitex/gen/misc.py +755 -0
- scitex/gen/path.py +0 -0
- scitex/gists/_SigMacro_processFigure_S.py +128 -0
- scitex/gists/_SigMacro_toBlue.py +172 -0
- scitex/gists/__init__.py +15 -0
- scitex/gists/guideline.md +42 -0
- scitex/git/README.md +121 -0
- scitex/git/__init__.py +61 -0
- scitex/git/_branch.py +185 -0
- scitex/git/_clone.py +167 -0
- scitex/git/_commit.py +170 -0
- scitex/git/_constants.py +17 -0
- scitex/git/_init.py +179 -0
- scitex/git/_remote.py +191 -0
- scitex/git/_result.py +70 -0
- scitex/git/_retry.py +113 -0
- scitex/git/_session.py +60 -0
- scitex/git/_types.py +136 -0
- scitex/git/_utils.py +28 -0
- scitex/git/_validation.py +117 -0
- scitex/git/_workflow.py +87 -0
- scitex/io/.gitkeep +0 -0
- scitex/io/README.md +376 -0
- scitex/io/__init__.py +116 -0
- scitex/io/_cache.py +101 -0
- scitex/io/_flush.py +27 -0
- scitex/io/_glob.py +109 -0
- scitex/io/_json2md.py +118 -0
- scitex/io/_load.py +453 -0
- scitex/io/_load_cache.py +303 -0
- scitex/io/_load_configs.py +175 -0
- scitex/io/_load_modules/_H5Explorer.py +352 -0
- scitex/io/_load_modules/_ZarrExplorer.py +114 -0
- scitex/io/_load_modules/__init__.py +41 -0
- scitex/io/_load_modules/_bibtex.py +206 -0
- scitex/io/_load_modules/_canvas.py +166 -0
- scitex/io/_load_modules/_catboost.py +71 -0
- scitex/io/_load_modules/_con.py +31 -0
- scitex/io/_load_modules/_docx.py +42 -0
- scitex/io/_load_modules/_eeg.py +125 -0
- scitex/io/_load_modules/_hdf5.py +73 -0
- scitex/io/_load_modules/_image.py +66 -0
- scitex/io/_load_modules/_joblib.py +19 -0
- scitex/io/_load_modules/_json.py +20 -0
- scitex/io/_load_modules/_markdown.py +103 -0
- scitex/io/_load_modules/_matlab.py +39 -0
- scitex/io/_load_modules/_numpy.py +39 -0
- scitex/io/_load_modules/_optuna.py +112 -0
- scitex/io/_load_modules/_pandas.py +75 -0
- scitex/io/_load_modules/_pdf.py +958 -0
- scitex/io/_load_modules/_pickle.py +24 -0
- scitex/io/_load_modules/_sqlite3.py +16 -0
- scitex/io/_load_modules/_torch.py +25 -0
- scitex/io/_load_modules/_txt.py +164 -0
- scitex/io/_load_modules/_xml.py +49 -0
- scitex/io/_load_modules/_yaml.py +24 -0
- scitex/io/_load_modules/_zarr.py +128 -0
- scitex/io/_metadata.py +47 -0
- scitex/io/_metadata_modules/__init__.py +46 -0
- scitex/io/_metadata_modules/_embed.py +70 -0
- scitex/io/_metadata_modules/_read.py +64 -0
- scitex/io/_metadata_modules/_utils.py +79 -0
- scitex/io/_metadata_modules/embed_metadata_jpeg.py +74 -0
- scitex/io/_metadata_modules/embed_metadata_pdf.py +53 -0
- scitex/io/_metadata_modules/embed_metadata_png.py +26 -0
- scitex/io/_metadata_modules/embed_metadata_svg.py +62 -0
- scitex/io/_metadata_modules/read_metadata_jpeg.py +57 -0
- scitex/io/_metadata_modules/read_metadata_pdf.py +51 -0
- scitex/io/_metadata_modules/read_metadata_png.py +39 -0
- scitex/io/_metadata_modules/read_metadata_svg.py +44 -0
- scitex/io/_mv_to_tmp.py +19 -0
- scitex/io/_path.py +286 -0
- scitex/io/_qr_utils.py +82 -0
- scitex/io/_reload.py +78 -0
- scitex/io/_save.py +497 -0
- scitex/io/_save_modules/__init__.py +130 -0
- scitex/io/_save_modules/_bibtex.py +199 -0
- scitex/io/_save_modules/_canvas.py +355 -0
- scitex/io/_save_modules/_catboost.py +22 -0
- scitex/io/_save_modules/_csv.py +95 -0
- scitex/io/_save_modules/_excel.py +210 -0
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_hdf5.py +262 -0
- scitex/io/_save_modules/_html.py +48 -0
- scitex/io/_save_modules/_image.py +300 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_joblib.py +25 -0
- scitex/io/_save_modules/_json.py +115 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_listed_dfs_as_csv.py +56 -0
- scitex/io/_save_modules/_listed_scalars_as_csv.py +42 -0
- scitex/io/_save_modules/_matlab.py +24 -0
- scitex/io/_save_modules/_mp4.py +29 -0
- scitex/io/_save_modules/_numpy.py +55 -0
- scitex/io/_save_modules/_optuna_study_as_csv_and_pngs.py +49 -0
- scitex/io/_save_modules/_pickle.py +45 -0
- scitex/io/_save_modules/_plot_bundle.py +112 -0
- scitex/io/_save_modules/_plot_scitex.py +536 -0
- scitex/io/_save_modules/_plotly.py +27 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_tex.py +278 -0
- scitex/io/_save_modules/_text.py +23 -0
- scitex/io/_save_modules/_torch.py +35 -0
- scitex/io/_save_modules/_yaml.py +68 -0
- scitex/io/_save_modules/_zarr.py +206 -0
- scitex/io/bundle/README.md +221 -0
- scitex/io/bundle/_Bundle.py +759 -0
- scitex/io/bundle/__init__.py +142 -0
- scitex/io/bundle/_children.py +208 -0
- scitex/io/bundle/_conversion/__init__.py +15 -0
- scitex/io/bundle/_conversion/_bundle2dict.py +44 -0
- scitex/io/bundle/_conversion/_dict2bundle.py +50 -0
- scitex/io/bundle/_core.py +651 -0
- scitex/io/bundle/_dataclasses/_Axes.py +57 -0
- scitex/io/bundle/_dataclasses/_BBox.py +54 -0
- scitex/io/bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/io/bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/io/bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/io/bundle/_dataclasses/_DataSource.py +44 -0
- scitex/io/bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/io/bundle/_dataclasses/_Spec.py +325 -0
- scitex/io/bundle/_dataclasses/_SpecRefs.py +45 -0
- scitex/io/bundle/_dataclasses/__init__.py +35 -0
- scitex/io/bundle/_extractors/__init__.py +32 -0
- scitex/io/bundle/_extractors/_extract_bar.py +131 -0
- scitex/io/bundle/_extractors/_extract_line.py +71 -0
- scitex/io/bundle/_extractors/_extract_scatter.py +79 -0
- scitex/io/bundle/_loader.py +134 -0
- scitex/io/bundle/_manifest.py +99 -0
- scitex/io/bundle/_mpl_helpers.py +480 -0
- scitex/io/bundle/_nested.py +726 -0
- scitex/io/bundle/_saver.py +268 -0
- scitex/io/bundle/_storage.py +200 -0
- scitex/io/bundle/_types.py +99 -0
- scitex/io/bundle/_validation.py +436 -0
- scitex/io/bundle/_zip.py +477 -0
- scitex/io/bundle/kinds/__init__.py +45 -0
- scitex/io/bundle/kinds/_figure/__init__.py +19 -0
- scitex/io/bundle/kinds/_figure/_composite.py +345 -0
- scitex/io/bundle/kinds/_plot/__init__.py +25 -0
- scitex/io/bundle/kinds/_plot/_backend/__init__.py +53 -0
- scitex/io/bundle/kinds/_plot/_backend/_export.py +165 -0
- scitex/io/bundle/kinds/_plot/_backend/_parser.py +188 -0
- scitex/io/bundle/kinds/_plot/_backend/_render.py +538 -0
- scitex/io/bundle/kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/io/bundle/kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/io/bundle/kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/io/bundle/kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/io/bundle/kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/io/bundle/kinds/_plot/_models/_Annotations.py +115 -0
- scitex/io/bundle/kinds/_plot/_models/_Axes.py +152 -0
- scitex/io/bundle/kinds/_plot/_models/_Figure.py +138 -0
- scitex/io/bundle/kinds/_plot/_models/_Guides.py +104 -0
- scitex/io/bundle/kinds/_plot/_models/_Plot.py +123 -0
- scitex/io/bundle/kinds/_plot/_models/_Styles.py +245 -0
- scitex/io/bundle/kinds/_plot/_models/__init__.py +80 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/io/bundle/kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/io/bundle/kinds/_plot/_utils/__init__.py +129 -0
- scitex/io/bundle/kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/io/bundle/kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/io/bundle/kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/io/bundle/kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/io/bundle/kinds/_plot/_utils/_get_template.py +178 -0
- scitex/io/bundle/kinds/_plot/_utils/_normalize.py +73 -0
- scitex/io/bundle/kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/io/bundle/kinds/_plot/_utils/_validate.py +197 -0
- scitex/io/bundle/kinds/_shape/__init__.py +141 -0
- scitex/io/bundle/kinds/_stats/__init__.py +56 -0
- scitex/io/bundle/kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/io/bundle/kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/io/bundle/kinds/_table/__init__.py +72 -0
- scitex/io/bundle/kinds/_table/_latex/__init__.py +93 -0
- scitex/io/bundle/kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/io/bundle/kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/io/bundle/kinds/_table/_latex/_export.py +279 -0
- scitex/io/bundle/kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/io/bundle/kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/io/bundle/kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/io/bundle/kinds/_table/_latex/_utils.py +369 -0
- scitex/io/bundle/kinds/_table/_latex/_validator.py +445 -0
- scitex/io/bundle/kinds/_text/__init__.py +77 -0
- scitex/io/bundle/schemas/__init__.py +30 -0
- scitex/io/bundle/schemas/data_info.schema.json +75 -0
- scitex/io/bundle/schemas/encoding.schema.json +90 -0
- scitex/io/bundle/schemas/node.schema.json +145 -0
- scitex/io/bundle/schemas/render_manifest.schema.json +62 -0
- scitex/io/bundle/schemas/stats.schema.json +132 -0
- scitex/io/bundle/schemas/theme.schema.json +141 -0
- scitex/io/utils/__init__.py +21 -0
- scitex/io/utils/h5_to_zarr.py +636 -0
- scitex/linalg/__init__.py +17 -0
- scitex/linalg/_distance.py +63 -0
- scitex/linalg/_geometric_median.py +69 -0
- scitex/linalg/_misc.py +73 -0
- scitex/logging/README.md +374 -0
- scitex/logging/_Tee.py +195 -0
- scitex/logging/__init__.py +216 -0
- scitex/logging/_config.py +161 -0
- scitex/logging/_context.py +105 -0
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +158 -0
- scitex/logging/_handlers.py +63 -0
- scitex/logging/_levels.py +27 -0
- scitex/logging/_logger.py +261 -0
- scitex/logging/_print_capture.py +95 -0
- scitex/logging/_warnings.py +261 -0
- scitex/mcp_server.py +91 -0
- scitex/ml/__init__.py +30 -0
- scitex/msword/__init__.py +255 -0
- scitex/msword/profiles.py +357 -0
- scitex/msword/reader.py +753 -0
- scitex/msword/utils.py +289 -0
- scitex/msword/writer.py +362 -0
- scitex/nn/_AxiswiseDropout.py +27 -0
- scitex/nn/_BNet.py +130 -0
- scitex/nn/_BNet_Res.py +164 -0
- scitex/nn/_ChannelGainChanger.py +44 -0
- scitex/nn/_DropoutChannels.py +50 -0
- scitex/nn/_Filters.py +493 -0
- scitex/nn/_FreqGainChanger.py +110 -0
- scitex/nn/_GaussianFilter.py +48 -0
- scitex/nn/_Hilbert.py +111 -0
- scitex/nn/_MNet_1000.py +157 -0
- scitex/nn/_ModulationIndex.py +223 -0
- scitex/nn/_PAC.py +415 -0
- scitex/nn/_PSD.py +39 -0
- scitex/nn/_ResNet1D.py +120 -0
- scitex/nn/_SpatialAttention.py +25 -0
- scitex/nn/_Spectrogram.py +170 -0
- scitex/nn/_SwapChannels.py +50 -0
- scitex/nn/_TransposeLayer.py +19 -0
- scitex/nn/_Wavelet.py +183 -0
- scitex/nn/__init__.py +75 -0
- scitex/os/__init__.py +8 -0
- scitex/os/_mv.py +50 -0
- scitex/parallel/__init__.py +8 -0
- scitex/parallel/_run.py +152 -0
- scitex/path/README.md +77 -0
- scitex/path/__init__.py +51 -0
- scitex/path/_clean.py +58 -0
- scitex/path/_find.py +90 -0
- scitex/path/_get_module_path.py +53 -0
- scitex/path/_get_spath.py +13 -0
- scitex/path/_getsize.py +32 -0
- scitex/path/_increment_version.py +80 -0
- scitex/path/_mk_spath.py +48 -0
- scitex/path/_path.py +12 -0
- scitex/path/_split.py +35 -0
- scitex/path/_symlink.py +343 -0
- scitex/path/_this_path.py +33 -0
- scitex/path/_version.py +102 -0
- scitex/pd/__init__.py +46 -0
- scitex/pd/_find_indi.py +126 -0
- scitex/pd/_find_pval.py +113 -0
- scitex/pd/_force_df.py +154 -0
- scitex/pd/_from_xyz.py +71 -0
- scitex/pd/_get_unique.py +100 -0
- scitex/pd/_ignore_SettingWithCopyWarning.py +34 -0
- scitex/pd/_melt_cols.py +81 -0
- scitex/pd/_merge_columns.py +221 -0
- scitex/pd/_mv.py +63 -0
- scitex/pd/_replace.py +62 -0
- scitex/pd/_round.py +97 -0
- scitex/pd/_slice.py +63 -0
- scitex/pd/_sort.py +91 -0
- scitex/pd/_to_numeric.py +53 -0
- scitex/pd/_to_xy.py +58 -0
- scitex/pd/_to_xyz.py +110 -0
- scitex/plt/README.md +1675 -0
- scitex/plt/__init__.py +803 -0
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_figrecipe_integration.py +121 -0
- scitex/plt/_mcp/__init__.py +4 -0
- scitex/plt/_mcp/_handlers_annotation.py +102 -0
- scitex/plt/_mcp/_handlers_figure.py +195 -0
- scitex/plt/_mcp/_handlers_plot.py +252 -0
- scitex/plt/_mcp/_handlers_style.py +219 -0
- scitex/plt/_mcp/handlers.py +74 -0
- scitex/plt/_mcp/tool_schemas.py +497 -0
- scitex/plt/_subplots/_AxesWrapper.py +197 -0
- scitex/plt/_subplots/_AxisWrapper.py +374 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +36 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +264 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +213 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +128 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +60 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +34 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +593 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +654 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +527 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +321 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +33 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +152 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +600 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_TrackingMixin.py +197 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +440 -0
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +91 -0
- scitex/plt/_subplots/_FigWrapper.py +475 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +329 -0
- scitex/plt/_subplots/__init__.py +121 -0
- scitex/plt/_subplots/_export_as_csv.py +467 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +83 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +72 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_bar.py +138 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_barh.py +58 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_boxplot.py +74 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_contour.py +50 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_errorbar.py +88 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +83 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_fill.py +46 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_fill_between.py +46 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hist.py +91 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +85 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +48 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +50 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +222 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +98 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +53 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +59 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +44 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +61 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_scatter.py +43 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_barplot.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_boxplot.py +112 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_heatmap.py +80 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_histplot.py +95 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +76 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_kdeplot.py +91 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +64 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +57 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_scatterplot.py +72 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_stripplot.py +80 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_swarmplot.py +80 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_violinplot.py +134 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +56 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +84 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +85 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_conf_mat.py +75 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +55 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_fillv.py +72 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +74 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +109 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +84 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_line.py +55 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +50 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +50 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +50 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +52 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_rectangle.py +129 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +51 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter_hist.py +92 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_shaded_line.py +72 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_violin.py +115 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_text.py +61 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_violin.py +65 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_violinplot.py +82 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +359 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/_tpl.py +29 -0
- scitex/plt/ax/__init__.py +123 -0
- scitex/plt/ax/_plot/__init__.py +79 -0
- scitex/plt/ax/_plot/_add_fitted_line.py +152 -0
- scitex/plt/ax/_plot/_plot_circular_hist.py +126 -0
- scitex/plt/ax/_plot/_plot_cube.py +57 -0
- scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +255 -0
- scitex/plt/ax/_plot/_stx_conf_mat.py +139 -0
- scitex/plt/ax/_plot/_stx_ecdf.py +113 -0
- scitex/plt/ax/_plot/_stx_fillv.py +57 -0
- scitex/plt/ax/_plot/_stx_heatmap.py +369 -0
- scitex/plt/ax/_plot/_stx_image.py +96 -0
- scitex/plt/ax/_plot/_stx_joyplot.py +135 -0
- scitex/plt/ax/_plot/_stx_raster.py +199 -0
- scitex/plt/ax/_plot/_stx_rectangle.py +70 -0
- scitex/plt/ax/_plot/_stx_scatter_hist.py +133 -0
- scitex/plt/ax/_plot/_stx_shaded_line.py +219 -0
- scitex/plt/ax/_plot/_stx_violin.py +352 -0
- scitex/plt/ax/_style/__init__.py +42 -0
- scitex/plt/ax/_style/_add_marginal_ax.py +46 -0
- scitex/plt/ax/_style/_add_panel.py +92 -0
- scitex/plt/ax/_style/_auto_scale_axis.py +199 -0
- scitex/plt/ax/_style/_extend.py +66 -0
- scitex/plt/ax/_style/_force_aspect.py +39 -0
- scitex/plt/ax/_style/_format_label.py +23 -0
- scitex/plt/ax/_style/_format_units.py +103 -0
- scitex/plt/ax/_style/_hide_spines.py +86 -0
- scitex/plt/ax/_style/_map_ticks.py +184 -0
- scitex/plt/ax/_style/_rotate_labels.py +320 -0
- scitex/plt/ax/_style/_rotate_labels_v01.py +258 -0
- scitex/plt/ax/_style/_sci_note.py +279 -0
- scitex/plt/ax/_style/_set_log_scale.py +334 -0
- scitex/plt/ax/_style/_set_meta.py +291 -0
- scitex/plt/ax/_style/_set_n_ticks.py +37 -0
- scitex/plt/ax/_style/_set_size.py +16 -0
- scitex/plt/ax/_style/_set_supxyt.py +133 -0
- scitex/plt/ax/_style/_set_ticks.py +276 -0
- scitex/plt/ax/_style/_set_xyt.py +130 -0
- scitex/plt/ax/_style/_share_axes.py +274 -0
- scitex/plt/ax/_style/_shift.py +139 -0
- scitex/plt/ax/_style/_show_spines.py +334 -0
- scitex/plt/ax/_style/_style_barplot.py +69 -0
- scitex/plt/ax/_style/_style_boxplot.py +155 -0
- scitex/plt/ax/_style/_style_errorbar.py +82 -0
- scitex/plt/ax/_style/_style_scatter.py +82 -0
- scitex/plt/ax/_style/_style_suptitles.py +76 -0
- scitex/plt/ax/_style/_style_violinplot.py +115 -0
- scitex/plt/color/_PARAMS.py +70 -0
- scitex/plt/color/__init__.py +84 -0
- scitex/plt/color/_add_hue_col.py +42 -0
- scitex/plt/color/_colors.py +204 -0
- scitex/plt/color/_get_colors_from_conf_matap.py +136 -0
- scitex/plt/color/_interpolate.py +29 -0
- scitex/plt/color/_vizualize_colors.py +53 -0
- scitex/plt/docs/FIGURE_ARCHITECTURE.md +315 -0
- scitex/plt/gallery/README.md +75 -0
- scitex/plt/gallery/__init__.py +126 -0
- scitex/plt/gallery/_generate.py +586 -0
- scitex/plt/gallery/_plots.py +594 -0
- scitex/plt/gallery/_registry.py +153 -0
- scitex/plt/gallery.md +170 -0
- scitex/plt/io/__init__.py +51 -0
- scitex/plt/io/_bundle.py +537 -0
- scitex/plt/io/_layered_bundle.py +1478 -0
- scitex/plt/mcp_server.py +231 -0
- scitex/plt/styles/SCITEX_STYLE.yaml +130 -0
- scitex/plt/styles/__init__.py +71 -0
- scitex/plt/styles/_plot_defaults.py +210 -0
- scitex/plt/styles/_plot_postprocess.py +487 -0
- scitex/plt/styles/_postprocess_helpers.py +158 -0
- scitex/plt/styles/_style_loader.py +268 -0
- scitex/plt/styles/presets.py +311 -0
- scitex/plt/utils/__init__.py +114 -0
- scitex/plt/utils/_calc_bacc_from_conf_mat.py +46 -0
- scitex/plt/utils/_calc_nice_ticks.py +101 -0
- scitex/plt/utils/_close.py +73 -0
- scitex/plt/utils/_collect_figure_metadata.py +3384 -0
- scitex/plt/utils/_colorbar.py +156 -0
- scitex/plt/utils/_configure_mpl.py +427 -0
- scitex/plt/utils/_crop.py +297 -0
- scitex/plt/utils/_csv_column_naming.py +474 -0
- scitex/plt/utils/_dimension_viewer.py +425 -0
- scitex/plt/utils/_figure_from_axes_mm.py +407 -0
- scitex/plt/utils/_figure_mm.py +472 -0
- scitex/plt/utils/_get_actual_font.py +57 -0
- scitex/plt/utils/_histogram_utils.py +134 -0
- scitex/plt/utils/_hitmap.py +1643 -0
- scitex/plt/utils/_im2grid.py +70 -0
- scitex/plt/utils/_is_valid_axis.py +82 -0
- scitex/plt/utils/_mk_colorbar.py +65 -0
- scitex/plt/utils/_mk_patches.py +26 -0
- scitex/plt/utils/_scientific_captions.py +701 -0
- scitex/plt/utils/_scitex_config.py +224 -0
- scitex/plt/utils/_units.py +123 -0
- scitex/plt/utils/metadata/__init__.py +61 -0
- scitex/plt/utils/metadata/_artist_extraction.py +119 -0
- scitex/plt/utils/metadata/_axes_metadata.py +93 -0
- scitex/plt/utils/metadata/_collection_artists.py +292 -0
- scitex/plt/utils/metadata/_core.py +207 -0
- scitex/plt/utils/metadata/_csv_column_extraction.py +186 -0
- scitex/plt/utils/metadata/_csv_hash.py +115 -0
- scitex/plt/utils/metadata/_csv_verification.py +95 -0
- scitex/plt/utils/metadata/_data_linkage.py +263 -0
- scitex/plt/utils/metadata/_dimensions.py +242 -0
- scitex/plt/utils/metadata/_editable_export.py +405 -0
- scitex/plt/utils/metadata/_figure_metadata.py +58 -0
- scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
- scitex/plt/utils/metadata/_image_text_artists.py +168 -0
- scitex/plt/utils/metadata/_label_parsing.py +82 -0
- scitex/plt/utils/metadata/_legend_extraction.py +120 -0
- scitex/plt/utils/metadata/_line_artists.py +367 -0
- scitex/plt/utils/metadata/_line_semantic_handling.py +173 -0
- scitex/plt/utils/metadata/_patch_artists.py +211 -0
- scitex/plt/utils/metadata/_plot_content.py +26 -0
- scitex/plt/utils/metadata/_plot_type_detection.py +184 -0
- scitex/plt/utils/metadata/_precision.py +134 -0
- scitex/plt/utils/metadata/_precision_config.py +68 -0
- scitex/plt/utils/metadata/_precision_sections.py +211 -0
- scitex/plt/utils/metadata/_recipe_extraction.py +267 -0
- scitex/plt/utils/metadata/_style_parsing.py +174 -0
- scitex/repro/README_RandomStateManager.md +211 -0
- scitex/repro/_RandomStateManager.py +684 -0
- scitex/repro/__init__.py +66 -0
- scitex/repro/_gen_ID.py +137 -0
- scitex/repro/_gen_timestamp.py +114 -0
- scitex/repro/_hash_array.py +139 -0
- scitex/reproduce/__init__.py +26 -0
- scitex/resource/README.md +81 -0
- scitex/resource/__init__.py +31 -0
- scitex/resource/_get_processor_usages.py +283 -0
- scitex/resource/_get_specs/info.yaml +122 -0
- scitex/resource/_get_specs/specs.yaml +129 -0
- scitex/resource/_get_specs.py +280 -0
- scitex/resource/_log_processor_usages.py +192 -0
- scitex/resource/_utils/__init__.py +31 -0
- scitex/resource/_utils/_get_env_info.py +469 -0
- scitex/resource/limit_ram.py +33 -0
- scitex/rng/__init__.py +32 -0
- scitex/schema/README.md +178 -0
- scitex/schema/__init__.py +222 -0
- scitex/schema/_canvas.py +444 -0
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_plot.py +1015 -0
- scitex/schema/_stats.py +762 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +492 -0
- scitex/scholar/.gitignore +23 -0
- scitex/scholar/README.md +122 -0
- scitex/scholar/__init__.py +354 -0
- scitex/scholar/__main__.py +339 -0
- scitex/scholar/_mcp/__init__.py +4 -0
- scitex/scholar/_mcp/all_handlers.py +74 -0
- scitex/scholar/_mcp/handlers.py +1478 -0
- scitex/scholar/_mcp/job_handlers.py +172 -0
- scitex/scholar/_mcp/job_tool_schemas.py +166 -0
- scitex/scholar/_mcp/tool_schemas.py +505 -0
- scitex/scholar/auth/README.md +83 -0
- scitex/scholar/auth/ScholarAuthManager.py +285 -0
- scitex/scholar/auth/__init__.py +21 -0
- scitex/scholar/auth/core/AuthenticationGateway.py +459 -0
- scitex/scholar/auth/core/BrowserAuthenticator.py +468 -0
- scitex/scholar/auth/core/StrategyResolver.py +292 -0
- scitex/scholar/auth/core/__init__.py +20 -0
- scitex/scholar/auth/gateway/_OpenURLLinkFinder.py +110 -0
- scitex/scholar/auth/gateway/_OpenURLResolver.py +201 -0
- scitex/scholar/auth/gateway/__init__.py +38 -0
- scitex/scholar/auth/gateway/_resolve_functions.py +101 -0
- scitex/scholar/auth/providers/BaseAuthenticator.py +167 -0
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +477 -0
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +436 -0
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +700 -0
- scitex/scholar/auth/providers/__init__.py +18 -0
- scitex/scholar/auth/providers/_notifications.py +417 -0
- scitex/scholar/auth/session/AuthCacheManager.py +186 -0
- scitex/scholar/auth/session/SessionManager.py +157 -0
- scitex/scholar/auth/session/__init__.py +11 -0
- scitex/scholar/auth/sso/BaseSSOAutomator.py +386 -0
- scitex/scholar/auth/sso/OpenAthensSSOAutomator.py +349 -0
- scitex/scholar/auth/sso/README.md +204 -0
- scitex/scholar/auth/sso/SSOAutomator.py +173 -0
- scitex/scholar/auth/sso/UniversityOfMelbourneSSOAutomator.py +363 -0
- scitex/scholar/auth/sso/__init__.py +15 -0
- scitex/scholar/browser/README.md +60 -0
- scitex/scholar/browser/ScholarBrowserManager.py +680 -0
- scitex/scholar/browser/__init__.py +39 -0
- scitex/scholar/browser/utils/README.md +96 -0
- scitex/scholar/browser/utils/__init__.py +13 -0
- scitex/scholar/browser/utils/click_and_wait.py +204 -0
- scitex/scholar/browser/utils/close_unwanted_pages.py +137 -0
- scitex/scholar/browser/utils/wait_redirects.py +707 -0
- scitex/scholar/citation_graph/README.md +117 -0
- scitex/scholar/citation_graph/__init__.py +28 -0
- scitex/scholar/citation_graph/builder.py +210 -0
- scitex/scholar/citation_graph/database.py +239 -0
- scitex/scholar/citation_graph/example.py +91 -0
- scitex/scholar/citation_graph/models.py +80 -0
- scitex/scholar/cli/README.md +368 -0
- scitex/scholar/cli/_CentralArgumentParser.py +137 -0
- scitex/scholar/cli/_argument_groups.py +222 -0
- scitex/scholar/cli/_doi_operations.py +56 -0
- scitex/scholar/cli/_url_utils.py +219 -0
- scitex/scholar/cli/chrome.py +101 -0
- scitex/scholar/cli/download_pdf.py +236 -0
- scitex/scholar/cli/handlers/__init__.py +23 -0
- scitex/scholar/cli/handlers/bibtex_handler.py +165 -0
- scitex/scholar/cli/handlers/doi_handler.py +63 -0
- scitex/scholar/cli/handlers/project_handler.py +261 -0
- scitex/scholar/cli/open_browser.py +297 -0
- scitex/scholar/cli/open_browser_auto.py +368 -0
- scitex/scholar/cli/open_browser_monitored.py +407 -0
- scitex/scholar/config/PublisherRules.py +132 -0
- scitex/scholar/config/README.md +31 -0
- scitex/scholar/config/ScholarConfig.py +143 -0
- scitex/scholar/config/__init__.py +18 -0
- scitex/scholar/config/_categories/README.md +149 -0
- scitex/scholar/config/_categories/api_keys.yaml +19 -0
- scitex/scholar/config/_categories/auth_gateway.yaml +149 -0
- scitex/scholar/config/_categories/authentication.yaml +24 -0
- scitex/scholar/config/_categories/browser.yaml +24 -0
- scitex/scholar/config/_categories/core.yaml +24 -0
- scitex/scholar/config/_categories/doi_publishers.yaml +53 -0
- scitex/scholar/config/_categories/notifications.yaml +11 -0
- scitex/scholar/config/_categories/pdf_download.yaml +15 -0
- scitex/scholar/config/_categories/publishers_pdf_rules.yaml +184 -0
- scitex/scholar/config/_categories/search_engines.yaml +22 -0
- scitex/scholar/config/_categories/url_finder_openurl.yaml +87 -0
- scitex/scholar/config/_categories/url_finder_selectors.yaml +147 -0
- scitex/scholar/config/core/_CascadeConfig.py +156 -0
- scitex/scholar/config/core/_PathManager.py +535 -0
- scitex/scholar/config/default.yaml +727 -0
- scitex/scholar/core/Paper.py +708 -0
- scitex/scholar/core/Papers.py +711 -0
- scitex/scholar/core/README.md +109 -0
- scitex/scholar/core/Scholar.py +1857 -0
- scitex/scholar/core/__init__.py +53 -0
- scitex/scholar/core/journal_normalizer.py +538 -0
- scitex/scholar/core/oa_cache.py +289 -0
- scitex/scholar/core/open_access.py +466 -0
- scitex/scholar/data/.gitkeep +0 -0
- scitex/scholar/data/README.md +44 -0
- scitex/scholar/data/bib_files/bibliography.bib +1952 -0
- scitex/scholar/data/bib_files/neurovista.bib +277 -0
- scitex/scholar/data/bib_files/neurovista_enriched.bib +441 -0
- scitex/scholar/data/bib_files/neurovista_enriched_enriched.bib +441 -0
- scitex/scholar/data/bib_files/neurovista_processed.bib +338 -0
- scitex/scholar/data/bib_files/openaccess.bib +89 -0
- scitex/scholar/data/bib_files/pac-seizure_prediction_enriched.bib +2178 -0
- scitex/scholar/data/bib_files/pac.bib +698 -0
- scitex/scholar/data/bib_files/pac_enriched.bib +1061 -0
- scitex/scholar/data/bib_files/pac_processed.bib +0 -0
- scitex/scholar/data/bib_files/pac_titles.txt +75 -0
- scitex/scholar/data/bib_files/paywalled.bib +98 -0
- scitex/scholar/data/bib_files/related-papers-by-coauthors.bib +58 -0
- scitex/scholar/data/bib_files/related-papers-by-coauthors_enriched.bib +87 -0
- scitex/scholar/data/bib_files/seizure_prediction.bib +694 -0
- scitex/scholar/data/bib_files/seizure_prediction_processed.bib +0 -0
- scitex/scholar/data/bib_files/test_complete_enriched.bib +437 -0
- scitex/scholar/data/bib_files/test_final_enriched.bib +437 -0
- scitex/scholar/data/bib_files/test_seizure.bib +46 -0
- scitex/scholar/data/impact_factor/JCR_IF_2022.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.db +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024.xlsx +0 -0
- scitex/scholar/data/impact_factor/JCR_IF_2024_v01.db +0 -0
- scitex/scholar/data/impact_factor.db +0 -0
- scitex/scholar/docs/00_SUMMARY.md +127 -0
- scitex/scholar/docs/01_STORAGE_ARCHITECTURE.md +118 -0
- scitex/scholar/docs/02_PIPELINE_ORGANIZATION.md +251 -0
- scitex/scholar/docs/03_PIPELINE_IMPLEMENTATION_PLAN.md +425 -0
- scitex/scholar/docs/04_PIPELINE_IMPLEMENTATION_COMPLETE.md +324 -0
- scitex/scholar/docs/05_DETAILS_FOR_DEVELOPERS.md +464 -0
- scitex/scholar/docs/06_IMPACT_FACTOR_CITATION_INTEGRATION.md +295 -0
- scitex/scholar/docs/bibfile.bib +66 -0
- scitex/scholar/docs/from_agents/OPENATHENS_SECURITY.md +101 -0
- scitex/scholar/docs/from_agents/feature-requests/scholar-openathens-authentication.md +107 -0
- scitex/scholar/docs/from_agents/scholar_enhancements_summary.md +66 -0
- scitex/scholar/docs/from_user/crawl4ai.md +15 -0
- scitex/scholar/docs/from_user/medium_article_on_logined_page_for_zenrows_1.md +363 -0
- scitex/scholar/docs/from_user/medium_article_on_logined_page_for_zenrows_2.md +1496 -0
- scitex/scholar/docs/from_user/papers.bib +698 -0
- scitex/scholar/docs/from_user/renamed-async_functions.md +970 -0
- scitex/scholar/docs/from_user/suggestions.md +97 -0
- scitex/scholar/docs/sample_data/PAYWALLED.json +84 -0
- scitex/scholar/docs/sample_data/openaccess.json +74 -0
- scitex/scholar/docs/sample_data/papers-enriched.bib +675 -0
- scitex/scholar/docs/sample_data/papers-partial-enriched.bib +601 -0
- scitex/scholar/docs/sample_data/test_papers.bib +18 -0
- scitex/scholar/docs/template.py +123 -0
- scitex/scholar/docs/zenrows_official/FAQ.md +434 -0
- scitex/scholar/docs/zenrows_official/captcha_integration.md +698 -0
- scitex/scholar/docs/zenrows_official/final_url.md +204 -0
- scitex/scholar/docs/zenrows_official/with_playwright.md +336 -0
- scitex/scholar/examples/00_config.py +270 -0
- scitex/scholar/examples/01_auth.py +165 -0
- scitex/scholar/examples/02_browser.py +169 -0
- scitex/scholar/examples/03_01-engine.py +486 -0
- scitex/scholar/examples/03_02-engine-for-bibtex.py +166 -0
- scitex/scholar/examples/04_01-url.py +218 -0
- scitex/scholar/examples/04_02-url-for-bibtex.py +283 -0
- scitex/scholar/examples/04_02-url-for-dois.py +305 -0
- scitex/scholar/examples/05_download_pdf.py +225 -0
- scitex/scholar/examples/06_find_and_download.py +217 -0
- scitex/scholar/examples/06_parse_bibtex.py +256 -0
- scitex/scholar/examples/07_storage_integration.py +253 -0
- scitex/scholar/examples/99_fullpipeline-for-bibtex.py +349 -0
- scitex/scholar/examples/99_fullpipeline-for-one-entry.py +293 -0
- scitex/scholar/examples/99_maintenance.py +134 -0
- scitex/scholar/examples/README.md +18 -0
- scitex/scholar/examples/SUGGESTIONS.md +865 -0
- scitex/scholar/examples/dev.py +38 -0
- scitex/scholar/examples/zotero_integration.py +246 -0
- scitex/scholar/impact_factor/ImpactFactorEngine.py +202 -0
- scitex/scholar/impact_factor/README.md +135 -0
- scitex/scholar/impact_factor/__init__.py +20 -0
- scitex/scholar/impact_factor/estimation/ImpactFactorEstimationEngine.py +0 -0
- scitex/scholar/impact_factor/estimation/README.md +351 -0
- scitex/scholar/impact_factor/estimation/__init__.py +39 -0
- scitex/scholar/impact_factor/estimation/build_database.py +0 -0
- scitex/scholar/impact_factor/estimation/core/__init__.py +24 -0
- scitex/scholar/impact_factor/estimation/core/cache_manager.py +535 -0
- scitex/scholar/impact_factor/estimation/core/calculator.py +389 -0
- scitex/scholar/impact_factor/estimation/core/journal_matcher.py +473 -0
- scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +323 -0
- scitex/scholar/impact_factor/jcr/README.md +84 -0
- scitex/scholar/impact_factor/jcr/TODO.md +28 -0
- scitex/scholar/impact_factor/jcr/build_database.py +268 -0
- scitex/scholar/integration/MIGRATION.md +138 -0
- scitex/scholar/integration/README.md +464 -0
- scitex/scholar/integration/__init__.py +59 -0
- scitex/scholar/integration/base.py +494 -0
- scitex/scholar/integration/mendeley/__init__.py +22 -0
- scitex/scholar/integration/mendeley/exporter.py +164 -0
- scitex/scholar/integration/mendeley/importer.py +236 -0
- scitex/scholar/integration/mendeley/linker.py +79 -0
- scitex/scholar/integration/mendeley/mapper.py +215 -0
- scitex/scholar/integration/zotero/README.md +336 -0
- scitex/scholar/integration/zotero/__init__.py +27 -0
- scitex/scholar/integration/zotero/__main__.py +247 -0
- scitex/scholar/integration/zotero/exporter.py +355 -0
- scitex/scholar/integration/zotero/importer.py +374 -0
- scitex/scholar/integration/zotero/linker.py +411 -0
- scitex/scholar/integration/zotero/mapper.py +291 -0
- scitex/scholar/jobs/_Job.py +266 -0
- scitex/scholar/jobs/_JobManager.py +458 -0
- scitex/scholar/jobs/__init__.py +74 -0
- scitex/scholar/jobs/_errors.py +346 -0
- scitex/scholar/jobs/_executors.py +373 -0
- scitex/scholar/mcp_server.py +365 -0
- scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSource.py +289 -0
- scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +229 -0
- scitex/scholar/metadata_engines/README.md +42 -0
- scitex/scholar/metadata_engines/ScholarEngine.py +574 -0
- scitex/scholar/metadata_engines/__init__.py +28 -0
- scitex/scholar/metadata_engines/individual/ArXivEngine.py +378 -0
- scitex/scholar/metadata_engines/individual/CrossRefEngine.py +264 -0
- scitex/scholar/metadata_engines/individual/CrossRefLocalEngine.py +303 -0
- scitex/scholar/metadata_engines/individual/OpenAlexEngine.py +340 -0
- scitex/scholar/metadata_engines/individual/PubMedEngine.py +312 -0
- scitex/scholar/metadata_engines/individual/SemanticScholarEngine.py +431 -0
- scitex/scholar/metadata_engines/individual/URLDOIEngine.py +399 -0
- scitex/scholar/metadata_engines/individual/_BaseDOIEngine.py +458 -0
- scitex/scholar/metadata_engines/individual/__init__.py +7 -0
- scitex/scholar/metadata_engines/utils/_PubMedConverter.py +465 -0
- scitex/scholar/metadata_engines/utils/_URLDOIExtractor.py +296 -0
- scitex/scholar/metadata_engines/utils/__init__.py +32 -0
- scitex/scholar/metadata_engines/utils/_metadata2bibtex.py +106 -0
- scitex/scholar/metadata_engines/utils/_standardize_metadata.py +375 -0
- scitex/scholar/pdf_download/README.md +51 -0
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +704 -0
- scitex/scholar/pdf_download/__init__.py +5 -0
- scitex/scholar/pdf_download/strategies/__init__.py +44 -0
- scitex/scholar/pdf_download/strategies/chrome_pdf_viewer.py +368 -0
- scitex/scholar/pdf_download/strategies/direct_download.py +127 -0
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +167 -0
- scitex/scholar/pdf_download/strategies/manual_download_utils.py +989 -0
- scitex/scholar/pdf_download/strategies/open_access_download.py +204 -0
- scitex/scholar/pdf_download/strategies/response_body.py +196 -0
- scitex/scholar/pipelines/README.md +172 -0
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +355 -0
- scitex/scholar/pipelines/ScholarPipelineMetadataParallel.py +269 -0
- scitex/scholar/pipelines/ScholarPipelineMetadataSingle.py +403 -0
- scitex/scholar/pipelines/ScholarPipelineParallel.py +462 -0
- scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +873 -0
- scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +553 -0
- scitex/scholar/pipelines/ScholarPipelineSingle.py +188 -0
- scitex/scholar/pipelines/SearchQueryParser.py +270 -0
- scitex/scholar/pipelines/__init__.py +55 -0
- scitex/scholar/pipelines/_single_steps.py +430 -0
- scitex/scholar/search_engines/ScholarSearchEngine.py +278 -0
- scitex/scholar/search_engines/_BaseSearchEngine.py +180 -0
- scitex/scholar/search_engines/__init__.py +0 -0
- scitex/scholar/search_engines/individual/ArXivSearchEngine.py +194 -0
- scitex/scholar/search_engines/individual/CrossRefSearchEngine.py +159 -0
- scitex/scholar/search_engines/individual/OpenAlexSearchEngine.py +177 -0
- scitex/scholar/search_engines/individual/PubMedSearchEngine.py +186 -0
- scitex/scholar/search_engines/individual/SemanticScholarSearchEngine.py +158 -0
- scitex/scholar/search_engines/individual/__init__.py +0 -0
- scitex/scholar/storage/BibTeXHandler.py +1151 -0
- scitex/scholar/storage/PaperIO.py +500 -0
- scitex/scholar/storage/ScholarLibrary.py +203 -0
- scitex/scholar/storage/_BibTeXValidator.py +571 -0
- scitex/scholar/storage/_DeduplicationManager.py +575 -0
- scitex/scholar/storage/_LibraryCacheManager.py +697 -0
- scitex/scholar/storage/_LibraryManager.py +1725 -0
- scitex/scholar/storage/__init__.py +42 -0
- scitex/scholar/url_finder/README.md +74 -0
- scitex/scholar/url_finder/ScholarURLFinder.py +364 -0
- scitex/scholar/url_finder/__init__.py +7 -0
- scitex/scholar/url_finder/docs/CORE_URL_TYPES.md +187 -0
- scitex/scholar/url_finder/docs/URL_SCHEMA.md +223 -0
- scitex/scholar/url_finder/strategies/__init__.py +33 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_direct_links.py +261 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_dropdown.py +67 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_href.py +200 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_navigation.py +254 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_publisher_patterns.py +158 -0
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_zotero_translators.py +164 -0
- scitex/scholar/url_finder/strategies/find_supplementary_urls_by_href.py +70 -0
- scitex/scholar/url_finder/translators/__init__.py +31 -0
- scitex/scholar/url_finder/translators/core/__init__.py +6 -0
- scitex/scholar/url_finder/translators/core/base.py +50 -0
- scitex/scholar/url_finder/translators/core/patterns.py +40 -0
- scitex/scholar/url_finder/translators/core/registry.py +372 -0
- scitex/scholar/url_finder/translators/individual/BOFiP_Impots.py +200 -0
- scitex/scholar/url_finder/translators/individual/Baidu_Scholar.py +229 -0
- scitex/scholar/url_finder/translators/individual/Bangkok_Post.py +152 -0
- scitex/scholar/url_finder/translators/individual/Baruch_Foundation.py +197 -0
- scitex/scholar/url_finder/translators/individual/Beobachter.py +193 -0
- scitex/scholar/url_finder/translators/individual/Bezneng_Gajit.py +203 -0
- scitex/scholar/url_finder/translators/individual/BibLaTeX.py +329 -0
- scitex/scholar/url_finder/translators/individual/BibTeX.py +330 -0
- scitex/scholar/url_finder/translators/individual/Biblio_com.py +222 -0
- scitex/scholar/url_finder/translators/individual/Bibliontology_RDF.py +228 -0
- scitex/scholar/url_finder/translators/individual/CLASE.py +23 -0
- scitex/scholar/url_finder/translators/individual/COBISS.py +23 -0
- scitex/scholar/url_finder/translators/individual/COinS.py +23 -0
- scitex/scholar/url_finder/translators/individual/CQ_Press.py +23 -0
- scitex/scholar/url_finder/translators/individual/CROSBI.py +23 -0
- scitex/scholar/url_finder/translators/individual/CSL_JSON.py +23 -0
- scitex/scholar/url_finder/translators/individual/CSV.py +23 -0
- scitex/scholar/url_finder/translators/individual/CalMatters.py +23 -0
- scitex/scholar/url_finder/translators/individual/Calisphere.py +23 -0
- scitex/scholar/url_finder/translators/individual/Camara_Brasileira_do_Livro_ISBN.py +290 -0
- scitex/scholar/url_finder/translators/individual/CanLII.py +151 -0
- scitex/scholar/url_finder/translators/individual/Canada_com.py +127 -0
- scitex/scholar/url_finder/translators/individual/Canadian_Letters_and_Images.py +152 -0
- scitex/scholar/url_finder/translators/individual/Canadiana_ca.py +195 -0
- scitex/scholar/url_finder/translators/individual/Cascadilla_Proceedings_Project.py +202 -0
- scitex/scholar/url_finder/translators/individual/Central_and_Eastern_European_Online_Library_Journals.py +172 -0
- scitex/scholar/url_finder/translators/individual/Champlain_Society_Collection.py +172 -0
- scitex/scholar/url_finder/translators/individual/Chicago_Journal_of_Theoretical_Computer_Science.py +210 -0
- scitex/scholar/url_finder/translators/individual/Christian_Science_Monitor.py +118 -0
- scitex/scholar/url_finder/translators/individual/Columbia_University_Press.py +232 -0
- scitex/scholar/url_finder/translators/individual/Common_Place.py +245 -0
- scitex/scholar/url_finder/translators/individual/Cornell_LII.py +165 -0
- scitex/scholar/url_finder/translators/individual/Cornell_University_Press.py +156 -0
- scitex/scholar/url_finder/translators/individual/CourtListener.py +173 -0
- scitex/scholar/url_finder/translators/individual/DAI_Zenon.py +160 -0
- scitex/scholar/url_finder/translators/individual/FULL_DOWNLOAD_LOG.txt +2 -0
- scitex/scholar/url_finder/translators/individual/FULL_DOWNLOAD_LOG_colored.txt +1 -0
- scitex/scholar/url_finder/translators/individual/__init__.py +25 -0
- scitex/scholar/url_finder/translators/individual/abc_news_australia.py +23 -0
- scitex/scholar/url_finder/translators/individual/access_engineering.py +23 -0
- scitex/scholar/url_finder/translators/individual/access_medicine.py +255 -0
- scitex/scholar/url_finder/translators/individual/access_science.py +23 -0
- scitex/scholar/url_finder/translators/individual/acls_humanities_ebook.py +23 -0
- scitex/scholar/url_finder/translators/individual/aclweb.py +23 -0
- scitex/scholar/url_finder/translators/individual/acm.py +11 -0
- scitex/scholar/url_finder/translators/individual/acm_digital_library.py +355 -0
- scitex/scholar/url_finder/translators/individual/acs.py +23 -0
- scitex/scholar/url_finder/translators/individual/acs_publications.py +23 -0
- scitex/scholar/url_finder/translators/individual/adam_matthew_digital.py +23 -0
- scitex/scholar/url_finder/translators/individual/ads_bibcode.py +23 -0
- scitex/scholar/url_finder/translators/individual/aea_web.py +23 -0
- scitex/scholar/url_finder/translators/individual/agencia_del_isbn.py +23 -0
- scitex/scholar/url_finder/translators/individual/agris.py +23 -0
- scitex/scholar/url_finder/translators/individual/ahval_news.py +23 -0
- scitex/scholar/url_finder/translators/individual/aip.py +23 -0
- scitex/scholar/url_finder/translators/individual/air_university_journals.py +23 -0
- scitex/scholar/url_finder/translators/individual/airiti.py +25 -0
- scitex/scholar/url_finder/translators/individual/alexander_street_press.py +23 -0
- scitex/scholar/url_finder/translators/individual/all_africa.py +23 -0
- scitex/scholar/url_finder/translators/individual/allafrica.py +23 -0
- scitex/scholar/url_finder/translators/individual/alsharekh.py +23 -0
- scitex/scholar/url_finder/translators/individual/alternet.py +23 -0
- scitex/scholar/url_finder/translators/individual/aluka.py +23 -0
- scitex/scholar/url_finder/translators/individual/amazon.py +23 -0
- scitex/scholar/url_finder/translators/individual/american_archive_public_broadcasting.py +23 -0
- scitex/scholar/url_finder/translators/individual/american_institute_aeronautics_astronautics.py +23 -0
- scitex/scholar/url_finder/translators/individual/american_prospect.py +23 -0
- scitex/scholar/url_finder/translators/individual/ams_journals.py +23 -0
- scitex/scholar/url_finder/translators/individual/ams_mathscinet.py +23 -0
- scitex/scholar/url_finder/translators/individual/ams_mathscinet_legacy.py +23 -0
- scitex/scholar/url_finder/translators/individual/ancestry_com_us_federal_census.py +23 -0
- scitex/scholar/url_finder/translators/individual/ancestry_us_federal_census.py +23 -0
- scitex/scholar/url_finder/translators/individual/annual_reviews.py +23 -0
- scitex/scholar/url_finder/translators/individual/antikvarium_hu.py +23 -0
- scitex/scholar/url_finder/translators/individual/aosic.py +25 -0
- scitex/scholar/url_finder/translators/individual/apa_psycnet.py +23 -0
- scitex/scholar/url_finder/translators/individual/apn_ru.py +23 -0
- scitex/scholar/url_finder/translators/individual/aps.py +23 -0
- scitex/scholar/url_finder/translators/individual/aps_physics.py +115 -0
- scitex/scholar/url_finder/translators/individual/aquadocs.py +23 -0
- scitex/scholar/url_finder/translators/individual/archeion.py +23 -0
- scitex/scholar/url_finder/translators/individual/archiv_fuer_sozialgeschichte.py +23 -0
- scitex/scholar/url_finder/translators/individual/archive_ouverte_aosic.py +25 -0
- scitex/scholar/url_finder/translators/individual/archive_ouverte_en_sciences_de_l_information_et_de_la_communication___aosic_.py +27 -0
- scitex/scholar/url_finder/translators/individual/archives_canada.py +23 -0
- scitex/scholar/url_finder/translators/individual/ariana_news.py +23 -0
- scitex/scholar/url_finder/translators/individual/art_institute_of_chicago.py +23 -0
- scitex/scholar/url_finder/translators/individual/artefacts_canada.py +23 -0
- scitex/scholar/url_finder/translators/individual/artfl_encyclopedie.py +23 -0
- scitex/scholar/url_finder/translators/individual/artforum.py +152 -0
- scitex/scholar/url_finder/translators/individual/artnet.py +23 -0
- scitex/scholar/url_finder/translators/individual/artnews.py +23 -0
- scitex/scholar/url_finder/translators/individual/artstor.py +23 -0
- scitex/scholar/url_finder/translators/individual/arxiv.py +39 -0
- scitex/scholar/url_finder/translators/individual/arxiv_org.py +122 -0
- scitex/scholar/url_finder/translators/individual/asce.py +23 -0
- scitex/scholar/url_finder/translators/individual/asco_meeting_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/astis.py +23 -0
- scitex/scholar/url_finder/translators/individual/atlanta_journal_constitution.py +145 -0
- scitex/scholar/url_finder/translators/individual/ats_international_journal.py +23 -0
- scitex/scholar/url_finder/translators/individual/atypon.py +23 -0
- scitex/scholar/url_finder/translators/individual/atypon_journals.py +253 -0
- scitex/scholar/url_finder/translators/individual/austlii_and_nzlii.py +251 -0
- scitex/scholar/url_finder/translators/individual/australian_dictionary_of_biography.py +124 -0
- scitex/scholar/url_finder/translators/individual/bailii.py +122 -0
- scitex/scholar/url_finder/translators/individual/bbc.py +229 -0
- scitex/scholar/url_finder/translators/individual/bbc_genome.py +180 -0
- scitex/scholar/url_finder/translators/individual/beck_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/biblioteca_nacional_de_maestros.py +144 -0
- scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationale_quebec_pistard.py +134 -0
- scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationales_quebec.py +187 -0
- scitex/scholar/url_finder/translators/individual/bibliotheque_et_archives_nationale_du_quebec__pistard_.py +23 -0
- scitex/scholar/url_finder/translators/individual/bibliotheque_et_archives_nationales_du_quebec.py +23 -0
- scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_de_france.py +23 -0
- scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_france.py +163 -0
- scitex/scholar/url_finder/translators/individual/bibsys.py +162 -0
- scitex/scholar/url_finder/translators/individual/bioconductor.py +184 -0
- scitex/scholar/url_finder/translators/individual/biomed_central.py +151 -0
- scitex/scholar/url_finder/translators/individual/bioone.py +23 -0
- scitex/scholar/url_finder/translators/individual/biorxiv.py +146 -0
- scitex/scholar/url_finder/translators/individual/blaetter.py +23 -0
- scitex/scholar/url_finder/translators/individual/blogger.py +148 -0
- scitex/scholar/url_finder/translators/individual/bloomberg.py +168 -0
- scitex/scholar/url_finder/translators/individual/bloomsbury_food_library.py +135 -0
- scitex/scholar/url_finder/translators/individual/bluesky.py +129 -0
- scitex/scholar/url_finder/translators/individual/bnf_isbn.py +118 -0
- scitex/scholar/url_finder/translators/individual/bocc.py +247 -0
- scitex/scholar/url_finder/translators/individual/boe.py +196 -0
- scitex/scholar/url_finder/translators/individual/bookmarks.py +23 -0
- scitex/scholar/url_finder/translators/individual/bookshop_org.py +23 -0
- scitex/scholar/url_finder/translators/individual/boston_review.py +23 -0
- scitex/scholar/url_finder/translators/individual/bosworth_toller_anglo_saxon_dictionary.py +23 -0
- scitex/scholar/url_finder/translators/individual/bosworth_toller_s_anglo_saxon_dictionary_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/bracero_history_archive.py +23 -0
- scitex/scholar/url_finder/translators/individual/brill.py +25 -0
- scitex/scholar/url_finder/translators/individual/brukerhandboken.py +23 -0
- scitex/scholar/url_finder/translators/individual/bryn_mawr_classical_review.py +23 -0
- scitex/scholar/url_finder/translators/individual/bundesgesetzblatt.py +23 -0
- scitex/scholar/url_finder/translators/individual/business_standard.py +178 -0
- scitex/scholar/url_finder/translators/individual/cabi___cab_abstracts.py +23 -0
- scitex/scholar/url_finder/translators/individual/cabi_cab_abstracts.py +176 -0
- scitex/scholar/url_finder/translators/individual/cairn.py +23 -0
- scitex/scholar/url_finder/translators/individual/cairn_info.py +23 -0
- scitex/scholar/url_finder/translators/individual/cambridge.py +25 -0
- scitex/scholar/url_finder/translators/individual/cambridge_core.py +178 -0
- scitex/scholar/url_finder/translators/individual/cambridge_engage_preprints.py +23 -0
- scitex/scholar/url_finder/translators/individual/caod.py +224 -0
- scitex/scholar/url_finder/translators/individual/cbc.py +281 -0
- scitex/scholar/url_finder/translators/individual/ccfr__bnf_.py +23 -0
- scitex/scholar/url_finder/translators/individual/ccfr_bnf.py +223 -0
- scitex/scholar/url_finder/translators/individual/cell_press.py +150 -0
- scitex/scholar/url_finder/translators/individual/cern_document_server.py +23 -0
- scitex/scholar/url_finder/translators/individual/ceur_workshop_proceedings.py +23 -0
- scitex/scholar/url_finder/translators/individual/cff.py +23 -0
- scitex/scholar/url_finder/translators/individual/cff_references.py +23 -0
- scitex/scholar/url_finder/translators/individual/champlain_society___collection.py +23 -0
- scitex/scholar/url_finder/translators/individual/chronicling_america.py +23 -0
- scitex/scholar/url_finder/translators/individual/cia_world_factbook.py +163 -0
- scitex/scholar/url_finder/translators/individual/cinii_research.py +23 -0
- scitex/scholar/url_finder/translators/individual/citavi_5_xml.py +23 -0
- scitex/scholar/url_finder/translators/individual/citeseer.py +23 -0
- scitex/scholar/url_finder/translators/individual/citizen_lab.py +23 -0
- scitex/scholar/url_finder/translators/individual/civilization_ca.py +23 -0
- scitex/scholar/url_finder/translators/individual/clacso.py +23 -0
- scitex/scholar/url_finder/translators/individual/climate_change_and_human_health_literature_portal.py +23 -0
- scitex/scholar/url_finder/translators/individual/climate_change_human_health.py +23 -0
- scitex/scholar/url_finder/translators/individual/clinical_key.py +23 -0
- scitex/scholar/url_finder/translators/individual/clinicaltrials_gov.py +23 -0
- scitex/scholar/url_finder/translators/individual/cnki.py +23 -0
- scitex/scholar/url_finder/translators/individual/code4lib_journal.py +23 -0
- scitex/scholar/url_finder/translators/individual/colorado_state_legislature.py +23 -0
- scitex/scholar/url_finder/translators/individual/computer_history_museum_archive.py +23 -0
- scitex/scholar/url_finder/translators/individual/copernicus.py +23 -0
- scitex/scholar/url_finder/translators/individual/crossref_rest.py +618 -0
- scitex/scholar/url_finder/translators/individual/crossref_unixref_xml.py +23 -0
- scitex/scholar/url_finder/translators/individual/csiro_publishing.py +23 -0
- scitex/scholar/url_finder/translators/individual/csiro_publishing_dup.py +23 -0
- scitex/scholar/url_finder/translators/individual/current_affairs.py +112 -0
- scitex/scholar/url_finder/translators/individual/dabi.py +205 -0
- scitex/scholar/url_finder/translators/individual/dagens_nyheter.py +25 -0
- scitex/scholar/url_finder/translators/individual/dagstuhl.py +147 -0
- scitex/scholar/url_finder/translators/individual/dagstuhl_research_online_publication_server.py +23 -0
- scitex/scholar/url_finder/translators/individual/dar_almandumah.py +160 -0
- scitex/scholar/url_finder/translators/individual/dart_europe.py +172 -0
- scitex/scholar/url_finder/translators/individual/data_gov.py +145 -0
- scitex/scholar/url_finder/translators/individual/databrary.py +144 -0
- scitex/scholar/url_finder/translators/individual/datacite_json.py +278 -0
- scitex/scholar/url_finder/translators/individual/dataverse.py +190 -0
- scitex/scholar/url_finder/translators/individual/daum_news.py +133 -0
- scitex/scholar/url_finder/translators/individual/dblp.py +151 -0
- scitex/scholar/url_finder/translators/individual/dblp_computer_science_bibliography.py +104 -0
- scitex/scholar/url_finder/translators/individual/dbpia.py +102 -0
- scitex/scholar/url_finder/translators/individual/de_gruyter_brill.py +23 -0
- scitex/scholar/url_finder/translators/individual/defense_technical_information_center.py +114 -0
- scitex/scholar/url_finder/translators/individual/dejure_org.py +23 -0
- scitex/scholar/url_finder/translators/individual/delpher.py +224 -0
- scitex/scholar/url_finder/translators/individual/demographic_research.py +160 -0
- scitex/scholar/url_finder/translators/individual/denik_cz.py +155 -0
- scitex/scholar/url_finder/translators/individual/depatisnet.py +145 -0
- scitex/scholar/url_finder/translators/individual/der_freitag.py +214 -0
- scitex/scholar/url_finder/translators/individual/der_spiegel.py +189 -0
- scitex/scholar/url_finder/translators/individual/desiring_god.py +23 -0
- scitex/scholar/url_finder/translators/individual/deutsche_fotothek.py +23 -0
- scitex/scholar/url_finder/translators/individual/deutsche_nationalbibliothek.py +23 -0
- scitex/scholar/url_finder/translators/individual/dhistory.py +23 -0
- scitex/scholar/url_finder/translators/individual/dialnet.py +23 -0
- scitex/scholar/url_finder/translators/individual/die_zeit.py +23 -0
- scitex/scholar/url_finder/translators/individual/digibib_net.py +25 -0
- scitex/scholar/url_finder/translators/individual/digital_humanities_quarterly.py +23 -0
- scitex/scholar/url_finder/translators/individual/digital_spy.py +23 -0
- scitex/scholar/url_finder/translators/individual/digizeitschriften.py +25 -0
- scitex/scholar/url_finder/translators/individual/dimensions.py +23 -0
- scitex/scholar/url_finder/translators/individual/dlibra.py +23 -0
- scitex/scholar/url_finder/translators/individual/doaj.py +23 -0
- scitex/scholar/url_finder/translators/individual/doi.py +23 -0
- scitex/scholar/url_finder/translators/individual/doi_content_negotiation.py +23 -0
- scitex/scholar/url_finder/translators/individual/douban.py +23 -0
- scitex/scholar/url_finder/translators/individual/dpla.py +200 -0
- scitex/scholar/url_finder/translators/individual/dreier_neuerscheinungsdienst.py +23 -0
- scitex/scholar/url_finder/translators/individual/drugbank_ca.py +23 -0
- scitex/scholar/url_finder/translators/individual/dryad.py +23 -0
- scitex/scholar/url_finder/translators/individual/dryad_digital_repository.py +23 -0
- scitex/scholar/url_finder/translators/individual/dspace.py +109 -0
- scitex/scholar/url_finder/translators/individual/dspace_intermediate_metadata.py +23 -0
- scitex/scholar/url_finder/translators/individual/duke_university_press_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/e_periodica_switzerland.py +23 -0
- scitex/scholar/url_finder/translators/individual/eastview.py +23 -0
- scitex/scholar/url_finder/translators/individual/ebrary.py +25 -0
- scitex/scholar/url_finder/translators/individual/ebsco_discovery_layer.py +23 -0
- scitex/scholar/url_finder/translators/individual/ebscohost.py +25 -0
- scitex/scholar/url_finder/translators/individual/edinburgh_university_press_journals.py +23 -0
- scitex/scholar/url_finder/translators/individual/education_week.py +23 -0
- scitex/scholar/url_finder/translators/individual/eidr.py +23 -0
- scitex/scholar/url_finder/translators/individual/el_comercio__peru_.py +23 -0
- scitex/scholar/url_finder/translators/individual/el_pais.py +23 -0
- scitex/scholar/url_finder/translators/individual/electronic_colloquium_on_computational_complexity.py +25 -0
- scitex/scholar/url_finder/translators/individual/elibrary_ru.py +23 -0
- scitex/scholar/url_finder/translators/individual/elicit.py +23 -0
- scitex/scholar/url_finder/translators/individual/elife.py +25 -0
- scitex/scholar/url_finder/translators/individual/elsevier_health.py +23 -0
- scitex/scholar/url_finder/translators/individual/elsevier_health_journals.py +25 -0
- scitex/scholar/url_finder/translators/individual/elsevier_pure.py +23 -0
- scitex/scholar/url_finder/translators/individual/embedded_metadata.py +23 -0
- scitex/scholar/url_finder/translators/individual/emedicine.py +23 -0
- scitex/scholar/url_finder/translators/individual/emerald.py +25 -0
- scitex/scholar/url_finder/translators/individual/emerald_insight.py +25 -0
- scitex/scholar/url_finder/translators/individual/emja.py +23 -0
- scitex/scholar/url_finder/translators/individual/encyclopedia_of_chicago.py +23 -0
- scitex/scholar/url_finder/translators/individual/encyclopedia_of_korean_culture.py +23 -0
- scitex/scholar/url_finder/translators/individual/endnote_xml.py +23 -0
- scitex/scholar/url_finder/translators/individual/engineering_village.py +23 -0
- scitex/scholar/url_finder/translators/individual/envidat.py +23 -0
- scitex/scholar/url_finder/translators/individual/epa_national_library_catalog.py +23 -0
- scitex/scholar/url_finder/translators/individual/epicurious.py +25 -0
- scitex/scholar/url_finder/translators/individual/eprint_iacr.py +23 -0
- scitex/scholar/url_finder/translators/individual/eric.py +23 -0
- scitex/scholar/url_finder/translators/individual/erudit.py +23 -0
- scitex/scholar/url_finder/translators/individual/espacenet.py +23 -0
- scitex/scholar/url_finder/translators/individual/etatar_ru.py +23 -0
- scitex/scholar/url_finder/translators/individual/euclid.py +23 -0
- scitex/scholar/url_finder/translators/individual/eur_lex.py +23 -0
- scitex/scholar/url_finder/translators/individual/eurasianet.py +23 -0
- scitex/scholar/url_finder/translators/individual/eurogamerusgamer.py +25 -0
- scitex/scholar/url_finder/translators/individual/europe_pmc.py +23 -0
- scitex/scholar/url_finder/translators/individual/evernote.py +23 -0
- scitex/scholar/url_finder/translators/individual/f1000_research.py +23 -0
- scitex/scholar/url_finder/translators/individual/fachportal_padagogik.py +25 -0
- scitex/scholar/url_finder/translators/individual/factiva.py +23 -0
- scitex/scholar/url_finder/translators/individual/failed_architecture.py +23 -0
- scitex/scholar/url_finder/translators/individual/fairfax_australia.py +23 -0
- scitex/scholar/url_finder/translators/individual/fao_publications.py +23 -0
- scitex/scholar/url_finder/translators/individual/fatcat.py +23 -0
- scitex/scholar/url_finder/translators/individual/faz_net.py +23 -0
- scitex/scholar/url_finder/translators/individual/feb_web_ru.py +23 -0
- scitex/scholar/url_finder/translators/individual/figshare.py +23 -0
- scitex/scholar/url_finder/translators/individual/financial_times.py +23 -0
- scitex/scholar/url_finder/translators/individual/finna.py +23 -0
- scitex/scholar/url_finder/translators/individual/fishpond_co_nz.py +23 -0
- scitex/scholar/url_finder/translators/individual/flickr.py +23 -0
- scitex/scholar/url_finder/translators/individual/foreign_affairs.py +23 -0
- scitex/scholar/url_finder/translators/individual/foreign_policy.py +23 -0
- scitex/scholar/url_finder/translators/individual/fr_online_de.py +23 -0
- scitex/scholar/url_finder/translators/individual/freecite.py +23 -0
- scitex/scholar/url_finder/translators/individual/freepatentsonline.py +23 -0
- scitex/scholar/url_finder/translators/individual/frieze.py +23 -0
- scitex/scholar/url_finder/translators/individual/frontiers.py +84 -0
- scitex/scholar/url_finder/translators/individual/gale_databases.py +25 -0
- scitex/scholar/url_finder/translators/individual/galegdc.py +23 -0
- scitex/scholar/url_finder/translators/individual/galegroup.py +23 -0
- scitex/scholar/url_finder/translators/individual/gallica.py +23 -0
- scitex/scholar/url_finder/translators/individual/game_studies.py +23 -0
- scitex/scholar/url_finder/translators/individual/gamespot.py +23 -0
- scitex/scholar/url_finder/translators/individual/gamestar_gamepro.py +23 -0
- scitex/scholar/url_finder/translators/individual/gasyrlar_awazy.py +23 -0
- scitex/scholar/url_finder/translators/individual/gemeinsamer_bibliotheksverbund_isbn.py +23 -0
- scitex/scholar/url_finder/translators/individual/gene_ontology.py +23 -0
- scitex/scholar/url_finder/translators/individual/github.py +23 -0
- scitex/scholar/url_finder/translators/individual/globes.py +23 -0
- scitex/scholar/url_finder/translators/individual/gmail.py +23 -0
- scitex/scholar/url_finder/translators/individual/gms_german_medical_science.py +111 -0
- scitex/scholar/url_finder/translators/individual/goodreads.py +23 -0
- scitex/scholar/url_finder/translators/individual/google_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/google_patents.py +23 -0
- scitex/scholar/url_finder/translators/individual/google_play.py +23 -0
- scitex/scholar/url_finder/translators/individual/google_presentation.py +23 -0
- scitex/scholar/url_finder/translators/individual/google_research.py +23 -0
- scitex/scholar/url_finder/translators/individual/google_scholar.py +23 -0
- scitex/scholar/url_finder/translators/individual/govinfo.py +23 -0
- scitex/scholar/url_finder/translators/individual/gpo_access_e_cfr.py +23 -0
- scitex/scholar/url_finder/translators/individual/gulag_many_days__many_lives.py +23 -0
- scitex/scholar/url_finder/translators/individual/haaretz.py +23 -0
- scitex/scholar/url_finder/translators/individual/habr.py +23 -0
- scitex/scholar/url_finder/translators/individual/hal.py +23 -0
- scitex/scholar/url_finder/translators/individual/hal_archives_ouvertes.py +23 -0
- scitex/scholar/url_finder/translators/individual/handelszeitung.py +23 -0
- scitex/scholar/url_finder/translators/individual/hanrei_watch.py +23 -0
- scitex/scholar/url_finder/translators/individual/harper_s_magazine.py +23 -0
- scitex/scholar/url_finder/translators/individual/harvard_business_review.py +23 -0
- scitex/scholar/url_finder/translators/individual/harvard_caselaw_access_project.py +23 -0
- scitex/scholar/url_finder/translators/individual/harvard_university_press_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/hathitrust.py +23 -0
- scitex/scholar/url_finder/translators/individual/hcsp.py +23 -0
- scitex/scholar/url_finder/translators/individual/heinonline.py +23 -0
- scitex/scholar/url_finder/translators/individual/heise.py +23 -0
- scitex/scholar/url_finder/translators/individual/herder.py +23 -0
- scitex/scholar/url_finder/translators/individual/highbeam.py +23 -0
- scitex/scholar/url_finder/translators/individual/highwire.py +23 -0
- scitex/scholar/url_finder/translators/individual/highwire2.py +23 -0
- scitex/scholar/url_finder/translators/individual/highwire_2_0.py +23 -0
- scitex/scholar/url_finder/translators/individual/hindawi.py +23 -0
- scitex/scholar/url_finder/translators/individual/hindawi_publishers.py +23 -0
- scitex/scholar/url_finder/translators/individual/hispanic_american_periodical_index.py +23 -0
- scitex/scholar/url_finder/translators/individual/homeland_security_digital_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/hudoc.py +23 -0
- scitex/scholar/url_finder/translators/individual/huff_post.py +23 -0
- scitex/scholar/url_finder/translators/individual/human_rights_watch.py +23 -0
- scitex/scholar/url_finder/translators/individual/ibisworld.py +23 -0
- scitex/scholar/url_finder/translators/individual/idea_alm.py +23 -0
- scitex/scholar/url_finder/translators/individual/idref.py +23 -0
- scitex/scholar/url_finder/translators/individual/ieee_computer_society.py +118 -0
- scitex/scholar/url_finder/translators/individual/ieee_xplore.py +265 -0
- scitex/scholar/url_finder/translators/individual/ietf.py +23 -0
- scitex/scholar/url_finder/translators/individual/ign.py +23 -0
- scitex/scholar/url_finder/translators/individual/imdb.py +23 -0
- scitex/scholar/url_finder/translators/individual/imf.py +23 -0
- scitex/scholar/url_finder/translators/individual/in_these_times.py +23 -0
- scitex/scholar/url_finder/translators/individual/informationssystem_medienpaedagogik.py +23 -0
- scitex/scholar/url_finder/translators/individual/informit_database.py +23 -0
- scitex/scholar/url_finder/translators/individual/infotrac.py +23 -0
- scitex/scholar/url_finder/translators/individual/ingenta_connect.py +23 -0
- scitex/scholar/url_finder/translators/individual/ingentaconnect.py +23 -0
- scitex/scholar/url_finder/translators/individual/inside_higher_ed.py +23 -0
- scitex/scholar/url_finder/translators/individual/insignia_opac.py +23 -0
- scitex/scholar/url_finder/translators/individual/inspire.py +23 -0
- scitex/scholar/url_finder/translators/individual/institute_of_contemporary_art.py +23 -0
- scitex/scholar/url_finder/translators/individual/institute_of_physics.py +23 -0
- scitex/scholar/url_finder/translators/individual/integrum.py +23 -0
- scitex/scholar/url_finder/translators/individual/intellixir.py +23 -0
- scitex/scholar/url_finder/translators/individual/inter_research_science_center.py +116 -0
- scitex/scholar/url_finder/translators/individual/international_nuclear_information_system.py +23 -0
- scitex/scholar/url_finder/translators/individual/internet_archive.py +23 -0
- scitex/scholar/url_finder/translators/individual/internet_archive_scholar.py +23 -0
- scitex/scholar/url_finder/translators/individual/internet_archive_wayback.py +23 -0
- scitex/scholar/url_finder/translators/individual/internet_archive_wayback_machine.py +23 -0
- scitex/scholar/url_finder/translators/individual/invenio_rdm.py +23 -0
- scitex/scholar/url_finder/translators/individual/inveniordm.py +23 -0
- scitex/scholar/url_finder/translators/individual/io_port.py +23 -0
- scitex/scholar/url_finder/translators/individual/iop.py +23 -0
- scitex/scholar/url_finder/translators/individual/ipcc.py +23 -0
- scitex/scholar/url_finder/translators/individual/isidore.py +23 -0
- scitex/scholar/url_finder/translators/individual/istc.py +23 -0
- scitex/scholar/url_finder/translators/individual/j_stage.py +23 -0
- scitex/scholar/url_finder/translators/individual/jahrbuch.py +23 -0
- scitex/scholar/url_finder/translators/individual/japan_times_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/jets.py +23 -0
- scitex/scholar/url_finder/translators/individual/jisc_historical_texts.py +25 -0
- scitex/scholar/url_finder/translators/individual/journal_of_electronic_publishing.py +23 -0
- scitex/scholar/url_finder/translators/individual/journal_of_extension.py +23 -0
- scitex/scholar/url_finder/translators/individual/journal_of_machine_learning_research.py +23 -0
- scitex/scholar/url_finder/translators/individual/journal_of_religion_and_society.py +23 -0
- scitex/scholar/url_finder/translators/individual/jrc_publications_repository.py +23 -0
- scitex/scholar/url_finder/translators/individual/jstage.py +23 -0
- scitex/scholar/url_finder/translators/individual/jstor.py +211 -0
- scitex/scholar/url_finder/translators/individual/juricaf.py +23 -0
- scitex/scholar/url_finder/translators/individual/jurion.py +23 -0
- scitex/scholar/url_finder/translators/individual/juris.py +23 -0
- scitex/scholar/url_finder/translators/individual/jurpc.py +23 -0
- scitex/scholar/url_finder/translators/individual/k10plus_isbn.py +23 -0
- scitex/scholar/url_finder/translators/individual/kanopy.py +23 -0
- scitex/scholar/url_finder/translators/individual/karger.py +23 -0
- scitex/scholar/url_finder/translators/individual/khaama_press.py +23 -0
- scitex/scholar/url_finder/translators/individual/kitapyurdu_com.py +23 -0
- scitex/scholar/url_finder/translators/individual/kommersant.py +23 -0
- scitex/scholar/url_finder/translators/individual/korean_national_library.py +25 -0
- scitex/scholar/url_finder/translators/individual/kstudy.py +23 -0
- scitex/scholar/url_finder/translators/individual/l_annee_philologique.py +23 -0
- scitex/scholar/url_finder/translators/individual/la_croix.py +23 -0
- scitex/scholar/url_finder/translators/individual/la_nacion__argentina_.py +23 -0
- scitex/scholar/url_finder/translators/individual/la_presse.py +23 -0
- scitex/scholar/url_finder/translators/individual/la_republica__peru_.py +23 -0
- scitex/scholar/url_finder/translators/individual/la_times.py +25 -0
- scitex/scholar/url_finder/translators/individual/lagen_nu.py +23 -0
- scitex/scholar/url_finder/translators/individual/landesbibliographie_baden_wurttemberg.py +25 -0
- scitex/scholar/url_finder/translators/individual/lapham_s_quarterly.py +23 -0
- scitex/scholar/url_finder/translators/individual/le_devoir.py +23 -0
- scitex/scholar/url_finder/translators/individual/le_figaro.py +23 -0
- scitex/scholar/url_finder/translators/individual/le_maitron.py +23 -0
- scitex/scholar/url_finder/translators/individual/le_monde.py +23 -0
- scitex/scholar/url_finder/translators/individual/le_monde_diplomatique.py +23 -0
- scitex/scholar/url_finder/translators/individual/legifrance.py +23 -0
- scitex/scholar/url_finder/translators/individual/legislative_insight.py +25 -0
- scitex/scholar/url_finder/translators/individual/lexis_.py +23 -0
- scitex/scholar/url_finder/translators/individual/lexisnexis.py +23 -0
- scitex/scholar/url_finder/translators/individual/libraries_tasmania.py +25 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__aleph_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__amicus_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__aquabrowser_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__bibliocommons_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__blacklight_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__capita_prism_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__dra_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__dynix_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__encore_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__innopac_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__koha_.py +25 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__mango_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__opals_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__pica2_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__pica_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__pika_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__polaris_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__quolto_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__rero_ils_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__sirsi_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__sirsi_elibrary_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__slims_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__tind_ils_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__tinread_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__tlcyouseemore_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__visual_library_2021_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__voyager_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__voyager_7_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_catalog__vtls_.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_genesis.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_hub_discover.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_of_congress_digital_collections.py +23 -0
- scitex/scholar/url_finder/translators/individual/library_of_congress_isbn.py +23 -0
- scitex/scholar/url_finder/translators/individual/libris_isbn.py +23 -0
- scitex/scholar/url_finder/translators/individual/lingbuzz.py +180 -0
- scitex/scholar/url_finder/translators/individual/lippincott_williams_and_wilkins.py +23 -0
- scitex/scholar/url_finder/translators/individual/literary_hub.py +23 -0
- scitex/scholar/url_finder/translators/individual/litres.py +23 -0
- scitex/scholar/url_finder/translators/individual/livejournal.py +23 -0
- scitex/scholar/url_finder/translators/individual/livivo.py +23 -0
- scitex/scholar/url_finder/translators/individual/london_review_of_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/lookus.py +23 -0
- scitex/scholar/url_finder/translators/individual/lulu.py +23 -0
- scitex/scholar/url_finder/translators/individual/lwn_net.py +23 -0
- scitex/scholar/url_finder/translators/individual/lww.py +23 -0
- scitex/scholar/url_finder/translators/individual/mab2.py +23 -0
- scitex/scholar/url_finder/translators/individual/magazines_russ_ru.py +23 -0
- scitex/scholar/url_finder/translators/individual/mailman.py +23 -0
- scitex/scholar/url_finder/translators/individual/mainichi_daily_news.py +23 -0
- scitex/scholar/url_finder/translators/individual/marc.py +23 -0
- scitex/scholar/url_finder/translators/individual/marcxml.py +23 -0
- scitex/scholar/url_finder/translators/individual/mastodon.py +23 -0
- scitex/scholar/url_finder/translators/individual/matbugat_ru.py +23 -0
- scitex/scholar/url_finder/translators/individual/max_planck_institute_for_the_history_of_science_virtual_laboratory_library.py +25 -0
- scitex/scholar/url_finder/translators/individual/mcv.py +23 -0
- scitex/scholar/url_finder/translators/individual/mdpi.py +76 -0
- scitex/scholar/url_finder/translators/individual/mdpi_journals.py +23 -0
- scitex/scholar/url_finder/translators/individual/medes.py +23 -0
- scitex/scholar/url_finder/translators/individual/medium.py +23 -0
- scitex/scholar/url_finder/translators/individual/medline_nbib.py +23 -0
- scitex/scholar/url_finder/translators/individual/medlinenbib.py +23 -0
- scitex/scholar/url_finder/translators/individual/medra.py +23 -0
- scitex/scholar/url_finder/translators/individual/metalib.py +23 -0
- scitex/scholar/url_finder/translators/individual/mets.py +23 -0
- scitex/scholar/url_finder/translators/individual/microbiology_society_journals.py +25 -0
- scitex/scholar/url_finder/translators/individual/microsoft_academic.py +23 -0
- scitex/scholar/url_finder/translators/individual/midas_journals.py +25 -0
- scitex/scholar/url_finder/translators/individual/mikromarc.py +23 -0
- scitex/scholar/url_finder/translators/individual/milli_kutuphane.py +23 -0
- scitex/scholar/url_finder/translators/individual/mit_press_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/mods.py +23 -0
- scitex/scholar/url_finder/translators/individual/mpg_pure.py +23 -0
- scitex/scholar/url_finder/translators/individual/musee_du_louvre.py +23 -0
- scitex/scholar/url_finder/translators/individual/nagoya_university_opac.py +25 -0
- scitex/scholar/url_finder/translators/individual/nasa_ads.py +23 -0
- scitex/scholar/url_finder/translators/individual/nasa_ntrs.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_academies_press.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_agriculture_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_archives_of_australia.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_archives_of_south_africa.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_bureau_of_economic_research.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_diet_library_catalogue.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_gallery_of_art___usa.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_gallery_of_australia.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_library_of_australia__new_catalog_.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_library_of_belarus.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_library_of_norway.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_library_of_poland_isbn.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_post.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_technical_reports_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/national_transportation_library_rosa_p.py +23 -0
- scitex/scholar/url_finder/translators/individual/nature.py +23 -0
- scitex/scholar/url_finder/translators/individual/nature_publishing_group.py +193 -0
- scitex/scholar/url_finder/translators/individual/nber.py +23 -0
- scitex/scholar/url_finder/translators/individual/ncbi_nucleotide.py +23 -0
- scitex/scholar/url_finder/translators/individual/neural_information_processing_systems.py +23 -0
- scitex/scholar/url_finder/translators/individual/new_left_review.py +23 -0
- scitex/scholar/url_finder/translators/individual/new_zealand_herald.py +23 -0
- scitex/scholar/url_finder/translators/individual/newlines_magazine.py +23 -0
- scitex/scholar/url_finder/translators/individual/news_corp_australia.py +23 -0
- scitex/scholar/url_finder/translators/individual/newsbank.py +23 -0
- scitex/scholar/url_finder/translators/individual/newshub_co_nz.py +23 -0
- scitex/scholar/url_finder/translators/individual/newsnettamedia.py +23 -0
- scitex/scholar/url_finder/translators/individual/newspapers_com.py +23 -0
- scitex/scholar/url_finder/translators/individual/noor_digital_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/note_html.py +23 -0
- scitex/scholar/url_finder/translators/individual/note_markdown.py +23 -0
- scitex/scholar/url_finder/translators/individual/notre_dame_philosophical_reviews.py +23 -0
- scitex/scholar/url_finder/translators/individual/npr.py +23 -0
- scitex/scholar/url_finder/translators/individual/nrc_nl.py +23 -0
- scitex/scholar/url_finder/translators/individual/nrc_research_press.py +23 -0
- scitex/scholar/url_finder/translators/individual/ntsb_accident_reports.py +25 -0
- scitex/scholar/url_finder/translators/individual/nypl_menus.py +23 -0
- scitex/scholar/url_finder/translators/individual/nypl_research_catalog.py +23 -0
- scitex/scholar/url_finder/translators/individual/nytimes_com.py +23 -0
- scitex/scholar/url_finder/translators/individual/nzz_ch.py +23 -0
- scitex/scholar/url_finder/translators/individual/oapen.py +23 -0
- scitex/scholar/url_finder/translators/individual/oclc_worldcat_firstsearch.py +23 -0
- scitex/scholar/url_finder/translators/individual/oecd.py +23 -0
- scitex/scholar/url_finder/translators/individual/oecd_ilibrary.py +23 -0
- scitex/scholar/url_finder/translators/individual/ohiolink.py +23 -0
- scitex/scholar/url_finder/translators/individual/open_conf.py +23 -0
- scitex/scholar/url_finder/translators/individual/open_knowledge_repository.py +23 -0
- scitex/scholar/url_finder/translators/individual/open_worldcat.py +23 -0
- scitex/scholar/url_finder/translators/individual/openalex.py +23 -0
- scitex/scholar/url_finder/translators/individual/openalex_json.py +23 -0
- scitex/scholar/url_finder/translators/individual/openedition_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/openedition_journals.py +161 -0
- scitex/scholar/url_finder/translators/individual/openjur.py +23 -0
- scitex/scholar/url_finder/translators/individual/optical_society_of_america.py +23 -0
- scitex/scholar/url_finder/translators/individual/optimization_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/orcid.py +286 -0
- scitex/scholar/url_finder/translators/individual/osa.py +23 -0
- scitex/scholar/url_finder/translators/individual/osf_preprints.py +23 -0
- scitex/scholar/url_finder/translators/individual/osti_energy_citations.py +23 -0
- scitex/scholar/url_finder/translators/individual/ovid.py +23 -0
- scitex/scholar/url_finder/translators/individual/ovid_tagged.py +23 -0
- scitex/scholar/url_finder/translators/individual/oxford.py +148 -0
- scitex/scholar/url_finder/translators/individual/oxford_dictionaries_premium.py +25 -0
- scitex/scholar/url_finder/translators/individual/oxford_english_dictionary.py +23 -0
- scitex/scholar/url_finder/translators/individual/oxford_music_and_art_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/oxford_reference.py +23 -0
- scitex/scholar/url_finder/translators/individual/oxford_university_press.py +23 -0
- scitex/scholar/url_finder/translators/individual/ozon_ru.py +25 -0
- scitex/scholar/url_finder/translators/individual/pajhwok_afghan_news.py +23 -0
- scitex/scholar/url_finder/translators/individual/papers_past.py +23 -0
- scitex/scholar/url_finder/translators/individual/paris_review.py +23 -0
- scitex/scholar/url_finder/translators/individual/pastebin.py +23 -0
- scitex/scholar/url_finder/translators/individual/patents___uspto.py +23 -0
- scitex/scholar/url_finder/translators/individual/pc_gamer.py +23 -0
- scitex/scholar/url_finder/translators/individual/pc_games.py +23 -0
- scitex/scholar/url_finder/translators/individual/peeters.py +23 -0
- scitex/scholar/url_finder/translators/individual/pei_archival_information_network.py +23 -0
- scitex/scholar/url_finder/translators/individual/pep_web.py +23 -0
- scitex/scholar/url_finder/translators/individual/perceiving_systems.py +23 -0
- scitex/scholar/url_finder/translators/individual/perlego.py +23 -0
- scitex/scholar/url_finder/translators/individual/philosopher_s_imprint.py +23 -0
- scitex/scholar/url_finder/translators/individual/philpapers.py +23 -0
- scitex/scholar/url_finder/translators/individual/pkp_catalog_systems.py +23 -0
- scitex/scholar/url_finder/translators/individual/pleade.py +23 -0
- scitex/scholar/url_finder/translators/individual/plos.py +111 -0
- scitex/scholar/url_finder/translators/individual/plos_journals.py +23 -0
- scitex/scholar/url_finder/translators/individual/pnas.py +23 -0
- scitex/scholar/url_finder/translators/individual/polygon.py +25 -0
- scitex/scholar/url_finder/translators/individual/potsdamer_neueste_nachrichten.py +23 -0
- scitex/scholar/url_finder/translators/individual/prc_history_review.py +23 -0
- scitex/scholar/url_finder/translators/individual/preprints_org.py +23 -0
- scitex/scholar/url_finder/translators/individual/primo.py +25 -0
- scitex/scholar/url_finder/translators/individual/primo_2018.py +23 -0
- scitex/scholar/url_finder/translators/individual/primo_normalized_xml.py +23 -0
- scitex/scholar/url_finder/translators/individual/probing_the_past.py +23 -0
- scitex/scholar/url_finder/translators/individual/project_gutenberg.py +23 -0
- scitex/scholar/url_finder/translators/individual/project_muse.py +25 -0
- scitex/scholar/url_finder/translators/individual/promed.py +23 -0
- scitex/scholar/url_finder/translators/individual/proquest.py +23 -0
- scitex/scholar/url_finder/translators/individual/proquest_ebook_central.py +23 -0
- scitex/scholar/url_finder/translators/individual/proquest_policyfile.py +23 -0
- scitex/scholar/url_finder/translators/individual/protein_data_bank.py +23 -0
- scitex/scholar/url_finder/translators/individual/pubfactory_journals.py +25 -0
- scitex/scholar/url_finder/translators/individual/public_record_office_victoria.py +23 -0
- scitex/scholar/url_finder/translators/individual/publications_du_quebec.py +23 -0
- scitex/scholar/url_finder/translators/individual/publications_office_of_the_european_union.py +23 -0
- scitex/scholar/url_finder/translators/individual/pubmed.py +330 -0
- scitex/scholar/url_finder/translators/individual/pubmed_central.py +71 -0
- scitex/scholar/url_finder/translators/individual/pubmed_xml.py +23 -0
- scitex/scholar/url_finder/translators/individual/pubpub.py +23 -0
- scitex/scholar/url_finder/translators/individual/pypi.py +23 -0
- scitex/scholar/url_finder/translators/individual/qatar_digital_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/queensland_state_archives.py +23 -0
- scitex/scholar/url_finder/translators/individual/r_packages.py +23 -0
- scitex/scholar/url_finder/translators/individual/radio_free_europe__radio_liberty.py +23 -0
- scitex/scholar/url_finder/translators/individual/rand.py +23 -0
- scitex/scholar/url_finder/translators/individual/rdf.py +23 -0
- scitex/scholar/url_finder/translators/individual/rechtspraak_nl.py +25 -0
- scitex/scholar/url_finder/translators/individual/redalyc.py +23 -0
- scitex/scholar/url_finder/translators/individual/reddit.py +23 -0
- scitex/scholar/url_finder/translators/individual/referbibix.py +23 -0
- scitex/scholar/url_finder/translators/individual/refworks_tagged.py +23 -0
- scitex/scholar/url_finder/translators/individual/regeringskansliet.py +23 -0
- scitex/scholar/url_finder/translators/individual/repec___econpapers.py +23 -0
- scitex/scholar/url_finder/translators/individual/repec___ideas.py +23 -0
- scitex/scholar/url_finder/translators/individual/repec_ideas.py +23 -0
- scitex/scholar/url_finder/translators/individual/research_square.py +23 -0
- scitex/scholar/url_finder/translators/individual/researchgate.py +23 -0
- scitex/scholar/url_finder/translators/individual/retsinformation.py +23 -0
- scitex/scholar/url_finder/translators/individual/reuters.py +23 -0
- scitex/scholar/url_finder/translators/individual/ris.py +23 -0
- scitex/scholar/url_finder/translators/individual/rock__paper__shotgun.py +23 -0
- scitex/scholar/url_finder/translators/individual/roll_call.py +23 -0
- scitex/scholar/url_finder/translators/individual/rsc.py +23 -0
- scitex/scholar/url_finder/translators/individual/rsc_publishing.py +23 -0
- scitex/scholar/url_finder/translators/individual/russian_state_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/sacramento_bee.py +23 -0
- scitex/scholar/url_finder/translators/individual/sae_papers.py +23 -0
- scitex/scholar/url_finder/translators/individual/safari_books_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/sage.py +23 -0
- scitex/scholar/url_finder/translators/individual/sage_journals.py +183 -0
- scitex/scholar/url_finder/translators/individual/sage_knowledge.py +23 -0
- scitex/scholar/url_finder/translators/individual/saildart.py +23 -0
- scitex/scholar/url_finder/translators/individual/salt_research_archives.py +23 -0
- scitex/scholar/url_finder/translators/individual/sbn_it.py +23 -0
- scitex/scholar/url_finder/translators/individual/scholars_portal_journals.py +23 -0
- scitex/scholar/url_finder/translators/individual/scholia.py +23 -0
- scitex/scholar/url_finder/translators/individual/schweizer_radio_und_fernsehen_srf.py +23 -0
- scitex/scholar/url_finder/translators/individual/scielo.py +23 -0
- scitex/scholar/url_finder/translators/individual/sciencedirect.py +132 -0
- scitex/scholar/url_finder/translators/individual/scinapse.py +110 -0
- scitex/scholar/url_finder/translators/individual/scopus.py +23 -0
- scitex/scholar/url_finder/translators/individual/semantic_scholar.py +23 -0
- scitex/scholar/url_finder/translators/individual/semantics_visual_library.py +25 -0
- scitex/scholar/url_finder/translators/individual/sfu_ipinch.py +23 -0
- scitex/scholar/url_finder/translators/individual/silverchair.py +301 -0
- scitex/scholar/url_finder/translators/individual/sipri.py +23 -0
- scitex/scholar/url_finder/translators/individual/sirs_knowledge_source.py +23 -0
- scitex/scholar/url_finder/translators/individual/slate.py +23 -0
- scitex/scholar/url_finder/translators/individual/slideshare.py +23 -0
- scitex/scholar/url_finder/translators/individual/slub_dresden.py +23 -0
- scitex/scholar/url_finder/translators/individual/sora.py +25 -0
- scitex/scholar/url_finder/translators/individual/springer.py +121 -0
- scitex/scholar/url_finder/translators/individual/springer_link.py +23 -0
- scitex/scholar/url_finder/translators/individual/ssoar.py +23 -0
- scitex/scholar/url_finder/translators/individual/ssrn.py +63 -0
- scitex/scholar/url_finder/translators/individual/stack_exchange.py +23 -0
- scitex/scholar/url_finder/translators/individual/standard_ebooks.py +23 -0
- scitex/scholar/url_finder/translators/individual/stanford_encyclopedia_of_philosophy.py +25 -0
- scitex/scholar/url_finder/translators/individual/stanford_university_press.py +23 -0
- scitex/scholar/url_finder/translators/individual/state_records_office_of_western_australia.py +23 -0
- scitex/scholar/url_finder/translators/individual/state_records_office_wa.py +23 -0
- scitex/scholar/url_finder/translators/individual/stitcher.py +23 -0
- scitex/scholar/url_finder/translators/individual/store_norske_leksikon.py +23 -0
- scitex/scholar/url_finder/translators/individual/stuff_co_nz.py +23 -0
- scitex/scholar/url_finder/translators/individual/substack.py +23 -0
- scitex/scholar/url_finder/translators/individual/sud_ouest.py +23 -0
- scitex/scholar/url_finder/translators/individual/sueddeutsche_de.py +23 -0
- scitex/scholar/url_finder/translators/individual/summon_2.py +23 -0
- scitex/scholar/url_finder/translators/individual/superlib.py +25 -0
- scitex/scholar/url_finder/translators/individual/svenska_dagbladet.py +23 -0
- scitex/scholar/url_finder/translators/individual/sveriges_radio.py +23 -0
- scitex/scholar/url_finder/translators/individual/svt_nyheter.py +23 -0
- scitex/scholar/url_finder/translators/individual/tagesspiegel.py +23 -0
- scitex/scholar/url_finder/translators/individual/talis_aspire.py +23 -0
- scitex/scholar/url_finder/translators/individual/talisprism.py +23 -0
- scitex/scholar/url_finder/translators/individual/tatknigafund.py +23 -0
- scitex/scholar/url_finder/translators/individual/tatpressa_ru.py +23 -0
- scitex/scholar/url_finder/translators/individual/taylor___francis_ebooks.py +23 -0
- scitex/scholar/url_finder/translators/individual/taylor_and_francis_nejm.py +23 -0
- scitex/scholar/url_finder/translators/individual/taylor_francis.py +23 -0
- scitex/scholar/url_finder/translators/individual/taylor_francis_nejm.py +23 -0
- scitex/scholar/url_finder/translators/individual/taz_de.py +23 -0
- scitex/scholar/url_finder/translators/individual/tei.py +23 -0
- scitex/scholar/url_finder/translators/individual/tesis_doctorals_en_xarxa.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_art_newspaper.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_atlantic.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_boston_globe.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_chronicle_of_higher_education.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_daily_beast.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_economic_times___the_times_of_india.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_economist.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_free_dictionary.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_globe_and_mail.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_guardian.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_hamilton_spectator.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_hindu.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_independent.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_intercept.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_met.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_microfinance_gateway.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_nation.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_national_archives__uk_.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_new_republic.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_new_york_review_of_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_new_yorker.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_open_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_straits_times.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_telegraph.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_times_and_sunday_times.py +23 -0
- scitex/scholar/url_finder/translators/individual/the_times_of_israel.py +23 -0
- scitex/scholar/url_finder/translators/individual/themarker.py +23 -0
- scitex/scholar/url_finder/translators/individual/theory_of_computing.py +23 -0
- scitex/scholar/url_finder/translators/individual/thesesfr.py +23 -0
- scitex/scholar/url_finder/translators/individual/thieme.py +23 -0
- scitex/scholar/url_finder/translators/individual/time_com.py +23 -0
- scitex/scholar/url_finder/translators/individual/timesmachine.py +23 -0
- scitex/scholar/url_finder/translators/individual/tony_blair_institute.py +23 -0
- scitex/scholar/url_finder/translators/individual/tony_blair_institute_for_global_change.py +23 -0
- scitex/scholar/url_finder/translators/individual/toronto_star.py +23 -0
- scitex/scholar/url_finder/translators/individual/transportation_research_board.py +23 -0
- scitex/scholar/url_finder/translators/individual/treesearch.py +25 -0
- scitex/scholar/url_finder/translators/individual/trove.py +23 -0
- scitex/scholar/url_finder/translators/individual/tumblr.py +23 -0
- scitex/scholar/url_finder/translators/individual/tv_by_the_numbers.py +23 -0
- scitex/scholar/url_finder/translators/individual/tvnz.py +23 -0
- scitex/scholar/url_finder/translators/individual/twitter.py +23 -0
- scitex/scholar/url_finder/translators/individual/ubiquity_journals.py +23 -0
- scitex/scholar/url_finder/translators/individual/uchicago_vufind.py +23 -0
- scitex/scholar/url_finder/translators/individual/unapi.py +23 -0
- scitex/scholar/url_finder/translators/individual/unesco.py +23 -0
- scitex/scholar/url_finder/translators/individual/university_of_california_press_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/university_of_chicago_press_books.py +25 -0
- scitex/scholar/url_finder/translators/individual/university_of_wisconsin_madison_libraries_catalog.py +23 -0
- scitex/scholar/url_finder/translators/individual/university_press_scholarship.py +23 -0
- scitex/scholar/url_finder/translators/individual/unqualified_dublin_core_rdf.py +23 -0
- scitex/scholar/url_finder/translators/individual/unz_print_archive.py +23 -0
- scitex/scholar/url_finder/translators/individual/upcommons.py +23 -0
- scitex/scholar/url_finder/translators/individual/uptodate_references.py +23 -0
- scitex/scholar/url_finder/translators/individual/us_national_archives_research_catalog.py +23 -0
- scitex/scholar/url_finder/translators/individual/usenix.py +23 -0
- scitex/scholar/url_finder/translators/individual/vanity_fair.py +23 -0
- scitex/scholar/url_finder/translators/individual/verniana.py +23 -0
- scitex/scholar/url_finder/translators/individual/verniana_jules_verne_studies.py +23 -0
- scitex/scholar/url_finder/translators/individual/verso_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/vice.py +23 -0
- scitex/scholar/url_finder/translators/individual/victoria___albert_museum.py +23 -0
- scitex/scholar/url_finder/translators/individual/vimeo.py +23 -0
- scitex/scholar/url_finder/translators/individual/vlex.py +25 -0
- scitex/scholar/url_finder/translators/individual/voxeu.py +23 -0
- scitex/scholar/url_finder/translators/individual/wall_street_journal.py +23 -0
- scitex/scholar/url_finder/translators/individual/wanfang_data.py +23 -0
- scitex/scholar/url_finder/translators/individual/washington_monthly.py +23 -0
- scitex/scholar/url_finder/translators/individual/washington_post.py +23 -0
- scitex/scholar/url_finder/translators/individual/web_of_science.py +25 -0
- scitex/scholar/url_finder/translators/individual/web_of_science_nextgen.py +25 -0
- scitex/scholar/url_finder/translators/individual/web_of_science_tagged.py +23 -0
- scitex/scholar/url_finder/translators/individual/welt_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/westlaw_uk.py +23 -0
- scitex/scholar/url_finder/translators/individual/who.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikidata.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikidata_quickstatements.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikileaks_plusd.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikimedia_commons.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikipedia.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikipedia_citation_templates.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikisource.py +23 -0
- scitex/scholar/url_finder/translators/individual/wikiwand.py +23 -0
- scitex/scholar/url_finder/translators/individual/wiktionary.py +23 -0
- scitex/scholar/url_finder/translators/individual/wildlife_biology_in_practice.py +23 -0
- scitex/scholar/url_finder/translators/individual/wiley.py +187 -0
- scitex/scholar/url_finder/translators/individual/wiley_online_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/wilson_center_digital_archive.py +25 -0
- scitex/scholar/url_finder/translators/individual/winnipeg_free_press.py +23 -0
- scitex/scholar/url_finder/translators/individual/wipo.py +23 -0
- scitex/scholar/url_finder/translators/individual/wired.py +23 -0
- scitex/scholar/url_finder/translators/individual/wiso.py +23 -0
- scitex/scholar/url_finder/translators/individual/womennews.py +23 -0
- scitex/scholar/url_finder/translators/individual/world_bank.py +23 -0
- scitex/scholar/url_finder/translators/individual/world_digital_library.py +23 -0
- scitex/scholar/url_finder/translators/individual/world_history_connected.py +23 -0
- scitex/scholar/url_finder/translators/individual/world_shakespeare_bibliography_online.py +23 -0
- scitex/scholar/url_finder/translators/individual/worldcat_discovery_service.py +23 -0
- scitex/scholar/url_finder/translators/individual/xml_contextobject.py +23 -0
- scitex/scholar/url_finder/translators/individual/yandex_books.py +23 -0
- scitex/scholar/url_finder/translators/individual/ynet.py +23 -0
- scitex/scholar/url_finder/translators/individual/youtube.py +23 -0
- scitex/scholar/url_finder/translators/individual/ypfs.py +23 -0
- scitex/scholar/url_finder/translators/individual/ypsf.py +23 -0
- scitex/scholar/url_finder/translators/individual/zbmath.py +102 -0
- scitex/scholar/url_finder/translators/individual/zenodo.py +23 -0
- scitex/scholar/url_finder/translators/individual/ziponline.py +23 -0
- scitex/scholar/url_finder/translators/individual/zobodat.py +23 -0
- scitex/scholar/url_finder/translators/individual/zotero_org.py +23 -0
- scitex/scholar/url_finder/translators/individual/zotero_rdf.py +23 -0
- scitex/scholar/url_finder/translators/individual/zoterobib.py +23 -0
- scitex/scholar/utils/__init__.py +25 -0
- scitex/scholar/utils/bibtex/__init__.py +9 -0
- scitex/scholar/utils/bibtex/_parse_bibtex.py +71 -0
- scitex/scholar/utils/cleanup/__init__.py +8 -0
- scitex/scholar/utils/cleanup/_cleanup_scholar_processes.py +92 -0
- scitex/scholar/utils/create_demo_movie.sh +49 -0
- scitex/scholar/utils/text/_TextNormalizer.py +480 -0
- scitex/scholar/utils/text/__init__.py +9 -0
- scitex/scholar/utils/validation/DOIValidator.py +292 -0
- scitex/scholar/utils/validation/README.md +169 -0
- scitex/scholar/utils/validation/__init__.py +13 -0
- scitex/scholar/utils/validation/validate_library_dois.py +225 -0
- scitex/scholar/zotero/__init__.py +38 -0
- scitex/security/README.md +255 -0
- scitex/security/__init__.py +34 -0
- scitex/security/cli.py +127 -0
- scitex/security/github.py +371 -0
- scitex/session/README.md +252 -0
- scitex/session/__init__.py +55 -0
- scitex/session/_decorator.py +631 -0
- scitex/session/_lifecycle.py +827 -0
- scitex/session/_manager.py +106 -0
- scitex/session/template.py +29 -0
- scitex/sh/README.md +58 -0
- scitex/sh/__init__.py +93 -0
- scitex/sh/_execute.py +224 -0
- scitex/sh/_security.py +67 -0
- scitex/sh/_types.py +28 -0
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/README.md +1085 -0
- scitex/stats/__init__.py +350 -0
- scitex/stats/__main__.py +281 -0
- scitex/stats/_figrecipe_integration.py +116 -0
- scitex/stats/_mcp/__init__.py +4 -0
- scitex/stats/_mcp/handlers.py +1191 -0
- scitex/stats/_mcp/tool_schemas.py +384 -0
- scitex/stats/_schema.py +50 -0
- scitex/stats/auto/__init__.py +187 -0
- scitex/stats/auto/_context.py +331 -0
- scitex/stats/auto/_formatting.py +679 -0
- scitex/stats/auto/_rules.py +901 -0
- scitex/stats/auto/_selector.py +554 -0
- scitex/stats/auto/_styles.py +721 -0
- scitex/stats/correct/__init__.py +21 -0
- scitex/stats/correct/_correct_bonferroni.py +560 -0
- scitex/stats/correct/_correct_fdr.py +608 -0
- scitex/stats/correct/_correct_fdr_.py +627 -0
- scitex/stats/correct/_correct_holm.py +530 -0
- scitex/stats/correct/_correct_sidak.py +514 -0
- scitex/stats/descriptive/__init__.py +85 -0
- scitex/stats/descriptive/_circular.py +400 -0
- scitex/stats/descriptive/_describe.py +182 -0
- scitex/stats/descriptive/_nan.py +288 -0
- scitex/stats/descriptive/_real.py +176 -0
- scitex/stats/effect_sizes/__init__.py +40 -0
- scitex/stats/effect_sizes/_cliffs_delta.py +328 -0
- scitex/stats/effect_sizes/_cohens_d.py +341 -0
- scitex/stats/effect_sizes/_epsilon_squared.py +312 -0
- scitex/stats/effect_sizes/_eta_squared.py +298 -0
- scitex/stats/effect_sizes/_prob_superiority.py +293 -0
- scitex/stats/io/__init__.py +28 -0
- scitex/stats/io/_bundle.py +156 -0
- scitex/stats/mcp_server.py +405 -0
- scitex/stats/posthoc/__init__.py +19 -0
- scitex/stats/posthoc/_dunnett.py +483 -0
- scitex/stats/posthoc/_games_howell.py +401 -0
- scitex/stats/posthoc/_tukey_hsd.py +375 -0
- scitex/stats/power/__init__.py +19 -0
- scitex/stats/power/_power.py +433 -0
- scitex/stats/run_all.sh +46 -0
- scitex/stats/tests/__init__.py +13 -0
- scitex/stats/tests/correlation/__init__.py +13 -0
- scitex/stats/tests/correlation/_test_pearson.py +252 -0
- scitex/stats/utils/__init__.py +59 -0
- scitex/stats/utils/_effect_size.py +992 -0
- scitex/stats/utils/_formatters.py +276 -0
- scitex/stats/utils/_normalizers.py +923 -0
- scitex/stats/utils/_power.py +433 -0
- scitex/str/__init__.py +112 -0
- scitex/str/_clean_path.py +79 -0
- scitex/str/_color_text.py +52 -0
- scitex/str/_decapitalize.py +58 -0
- scitex/str/_factor_out_digits.py +309 -0
- scitex/str/_format_plot_text.py +567 -0
- scitex/str/_grep.py +48 -0
- scitex/str/_latex.py +97 -0
- scitex/str/_latex_fallback.py +583 -0
- scitex/str/_mask_api.py +39 -0
- scitex/str/_mask_api_key.py +8 -0
- scitex/str/_parse.py +163 -0
- scitex/str/_print_block.py +47 -0
- scitex/str/_print_debug.py +68 -0
- scitex/str/_printc.py +62 -0
- scitex/str/_readable_bytes.py +38 -0
- scitex/str/_remove_ansi.py +23 -0
- scitex/str/_replace.py +134 -0
- scitex/str/_search.py +127 -0
- scitex/str/_squeeze_space.py +36 -0
- scitex/template/README.md +154 -0
- scitex/template/__init__.py +115 -0
- scitex/template/_clone_project.py +167 -0
- scitex/template/_copy.py +105 -0
- scitex/template/_customize.py +116 -0
- scitex/template/_git_strategy.py +122 -0
- scitex/template/_logging_helpers.py +122 -0
- scitex/template/_mcp/__init__.py +4 -0
- scitex/template/_mcp/handlers.py +259 -0
- scitex/template/_mcp/tool_schemas.py +112 -0
- scitex/template/_rename.py +65 -0
- scitex/template/clone_pip_project.py +106 -0
- scitex/template/clone_research.py +106 -0
- scitex/template/clone_singularity.py +106 -0
- scitex/template/clone_writer_directory.py +106 -0
- scitex/template/mcp_server.py +186 -0
- scitex/tex/__init__.py +14 -0
- scitex/tex/_export.py +890 -0
- scitex/tex/_preview.py +118 -0
- scitex/tex/_to_vec.py +119 -0
- scitex/torch/__init__.py +28 -0
- scitex/torch/_apply_to.py +34 -0
- scitex/torch/_nan_funcs.py +77 -0
- scitex/types/_ArrayLike.py +66 -0
- scitex/types/_ColorLike.py +21 -0
- scitex/types/__init__.py +14 -0
- scitex/types/_is_listed_X.py +70 -0
- scitex/ui/__init__.py +173 -0
- scitex/ui/_backends/__init__.py +66 -0
- scitex/ui/_backends/_audio.py +86 -0
- scitex/ui/_backends/_config.py +250 -0
- scitex/ui/_backends/_desktop.py +184 -0
- scitex/ui/_backends/_emacs.py +210 -0
- scitex/ui/_backends/_email.py +78 -0
- scitex/ui/_backends/_matplotlib.py +118 -0
- scitex/ui/_backends/_playwright.py +109 -0
- scitex/ui/_backends/_types.py +58 -0
- scitex/ui/_backends/_webhook.py +77 -0
- scitex/ui/_backends.py +37 -0
- scitex/ui/_mcp/__init__.py +23 -0
- scitex/ui/_mcp/handlers.py +260 -0
- scitex/ui/_mcp/tool_schemas.py +107 -0
- scitex/ui/mcp_server.py +151 -0
- scitex/units.py +324 -0
- scitex/utils/__init__.py +22 -0
- scitex/utils/_compress_hdf5.py +127 -0
- scitex/utils/_email.py +221 -0
- scitex/utils/_grid.py +150 -0
- scitex/utils/_notify.py +267 -0
- scitex/utils/_search.py +123 -0
- scitex/utils/_verify_scitex_format.py +564 -0
- scitex/utils/_verify_scitex_format_v01.py +348 -0
- scitex/utils/template.py +123 -0
- scitex/web/__init__.py +49 -0
- scitex/web/_scraping.py +150 -0
- scitex/web/_search_pubmed.py +501 -0
- scitex/web/_summarize_url.py +158 -0
- scitex/web/download_images.py +316 -0
- scitex/writer/README.md +435 -0
- scitex/writer/Writer.py +487 -0
- scitex/writer/__init__.py +46 -0
- scitex/writer/_clone_writer_project.py +160 -0
- scitex/writer/_compile/__init__.py +41 -0
- scitex/writer/_compile/_compile_async.py +130 -0
- scitex/writer/_compile/_compile_unified.py +148 -0
- scitex/writer/_compile/_parser.py +63 -0
- scitex/writer/_compile/_runner.py +457 -0
- scitex/writer/_compile/_validator.py +46 -0
- scitex/writer/_compile/manuscript.py +110 -0
- scitex/writer/_compile/revision.py +82 -0
- scitex/writer/_compile/supplementary.py +100 -0
- scitex/writer/_dataclasses/__init__.py +44 -0
- scitex/writer/_dataclasses/config/_CONSTANTS.py +46 -0
- scitex/writer/_dataclasses/config/_WriterConfig.py +175 -0
- scitex/writer/_dataclasses/config/__init__.py +9 -0
- scitex/writer/_dataclasses/contents/_ManuscriptContents.py +236 -0
- scitex/writer/_dataclasses/contents/_RevisionContents.py +136 -0
- scitex/writer/_dataclasses/contents/_SupplementaryContents.py +114 -0
- scitex/writer/_dataclasses/contents/__init__.py +9 -0
- scitex/writer/_dataclasses/core/_Document.py +146 -0
- scitex/writer/_dataclasses/core/_DocumentSection.py +546 -0
- scitex/writer/_dataclasses/core/__init__.py +7 -0
- scitex/writer/_dataclasses/results/_CompilationResult.py +165 -0
- scitex/writer/_dataclasses/results/_LaTeXIssue.py +102 -0
- scitex/writer/_dataclasses/results/_SaveSectionsResponse.py +118 -0
- scitex/writer/_dataclasses/results/_SectionReadResponse.py +131 -0
- scitex/writer/_dataclasses/results/__init__.py +11 -0
- scitex/writer/_dataclasses/tree/MINIMUM_FILES.md +121 -0
- scitex/writer/_dataclasses/tree/_ConfigTree.py +86 -0
- scitex/writer/_dataclasses/tree/_ManuscriptTree.py +84 -0
- scitex/writer/_dataclasses/tree/_RevisionTree.py +97 -0
- scitex/writer/_dataclasses/tree/_ScriptsTree.py +118 -0
- scitex/writer/_dataclasses/tree/_SharedTree.py +100 -0
- scitex/writer/_dataclasses/tree/_SupplementaryTree.py +101 -0
- scitex/writer/_dataclasses/tree/__init__.py +23 -0
- scitex/writer/_mcp/__init__.py +4 -0
- scitex/writer/_mcp/handlers.py +765 -0
- scitex/writer/_mcp/tool_schemas.py +363 -0
- scitex/writer/_project/__init__.py +29 -0
- scitex/writer/_project/_create.py +89 -0
- scitex/writer/_project/_trees.py +63 -0
- scitex/writer/_project/_validate.py +61 -0
- scitex/writer/utils/.legacy_git_retry.py +164 -0
- scitex/writer/utils/__init__.py +24 -0
- scitex/writer/utils/_converters.py +635 -0
- scitex/writer/utils/_parse_latex_logs.py +138 -0
- scitex/writer/utils/_parse_script_args.py +156 -0
- scitex/writer/utils/_verify_tree_structure.py +205 -0
- scitex/writer/utils/_watch.py +96 -0
- scitex-2.14.0.dist-info/METADATA +1238 -0
- scitex-2.14.0.dist-info/RECORD +2498 -0
- scitex-2.14.0.dist-info/WHEEL +4 -0
- scitex-2.14.0.dist-info/entry_points.txt +12 -0
- scitex-2.14.0.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,3384 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-11-19 13:00:00 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/plt/utils/_collect_figure_metadata.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Collect metadata from matplotlib figures for embedding in saved images.
|
|
8
|
+
|
|
9
|
+
This module provides utilities to automatically extract dimension, styling,
|
|
10
|
+
and configuration information from matplotlib figures and axes, making saved
|
|
11
|
+
figures self-documenting and reproducible.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__FILE__ = __file__
|
|
15
|
+
|
|
16
|
+
from typing import Dict, Optional, Union, List
|
|
17
|
+
|
|
18
|
+
from scitex import logging
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# Precision settings for JSON output
|
|
23
|
+
PRECISION = {
|
|
24
|
+
"mm": 2, # Millimeters: 0.01mm precision (10 microns)
|
|
25
|
+
"inch": 3, # Inches: 0.001 inch precision
|
|
26
|
+
"position": 3, # Normalized position: 0.001 precision
|
|
27
|
+
"lim": 2, # Axis limits: 2 decimal places
|
|
28
|
+
"linewidth": 2, # Line widths: 0.01 precision
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FixedFloat:
|
|
33
|
+
"""
|
|
34
|
+
A float wrapper that preserves fixed decimal places in JSON output.
|
|
35
|
+
|
|
36
|
+
Example: FixedFloat(0.25, 3) -> "0.250" in JSON
|
|
37
|
+
"""
|
|
38
|
+
def __init__(self, value: float, precision: int):
|
|
39
|
+
self.value = round(value, precision)
|
|
40
|
+
self.precision = precision
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return f"{self.value:.{self.precision}f}"
|
|
44
|
+
|
|
45
|
+
def __float__(self):
|
|
46
|
+
return self.value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _round_value(value: Union[float, int], precision: int, fixed: bool = False) -> Union[float, int, "FixedFloat"]:
|
|
50
|
+
"""
|
|
51
|
+
Round a single value to specified precision.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
value : float or int
|
|
56
|
+
Value to round
|
|
57
|
+
precision : int
|
|
58
|
+
Number of decimal places
|
|
59
|
+
fixed : bool
|
|
60
|
+
If True, return FixedFloat with fixed decimal places (e.g., 0.250)
|
|
61
|
+
If False, return float (e.g., 0.25)
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(value, int):
|
|
64
|
+
if fixed:
|
|
65
|
+
return FixedFloat(float(value), precision)
|
|
66
|
+
return value
|
|
67
|
+
if isinstance(value, float):
|
|
68
|
+
if fixed:
|
|
69
|
+
return FixedFloat(value, precision)
|
|
70
|
+
return round(value, precision)
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _round_list(values: List, precision: int, fixed: bool = False) -> List:
|
|
75
|
+
"""Round all values in a list."""
|
|
76
|
+
return [_round_value(v, precision, fixed) for v in values]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _round_dict(d: dict, precision_map: dict = None) -> dict:
|
|
80
|
+
"""
|
|
81
|
+
Round all float values in a dict based on key-specific precision.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
d : dict
|
|
86
|
+
Dictionary to process
|
|
87
|
+
precision_map : dict, optional
|
|
88
|
+
Mapping of key patterns to precision values.
|
|
89
|
+
Default uses PRECISION settings based on key names.
|
|
90
|
+
"""
|
|
91
|
+
if precision_map is None:
|
|
92
|
+
precision_map = {}
|
|
93
|
+
|
|
94
|
+
result = {}
|
|
95
|
+
for key, value in d.items():
|
|
96
|
+
# Determine precision based on key name
|
|
97
|
+
if "mm" in key.lower():
|
|
98
|
+
prec = PRECISION["mm"]
|
|
99
|
+
elif "inch" in key.lower():
|
|
100
|
+
prec = PRECISION["inch"]
|
|
101
|
+
elif "position" in key.lower() or key in ("left", "bottom", "right", "top"):
|
|
102
|
+
prec = PRECISION["position"]
|
|
103
|
+
elif "lim" in key.lower():
|
|
104
|
+
prec = PRECISION["lim"]
|
|
105
|
+
elif "width" in key.lower() and "line" in key.lower():
|
|
106
|
+
prec = PRECISION["linewidth"]
|
|
107
|
+
else:
|
|
108
|
+
prec = precision_map.get(key, 3) # Default 3 decimals
|
|
109
|
+
|
|
110
|
+
if isinstance(value, dict):
|
|
111
|
+
result[key] = _round_dict(value, precision_map)
|
|
112
|
+
elif isinstance(value, list):
|
|
113
|
+
result[key] = _round_list(value, prec)
|
|
114
|
+
elif isinstance(value, float):
|
|
115
|
+
result[key] = _round_value(value, prec)
|
|
116
|
+
else:
|
|
117
|
+
result[key] = value
|
|
118
|
+
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _collect_single_axes_metadata(fig, ax, ax_index: int) -> dict:
|
|
123
|
+
"""
|
|
124
|
+
Collect metadata for a single axes object.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
fig : matplotlib.figure.Figure
|
|
129
|
+
The parent figure
|
|
130
|
+
ax : matplotlib.axes.Axes
|
|
131
|
+
The axes to collect metadata from
|
|
132
|
+
ax_index : int
|
|
133
|
+
Index of this axes in the figure (for position tracking)
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
dict
|
|
138
|
+
Metadata dictionary for this axes containing:
|
|
139
|
+
- size_mm, size_inch, size_px
|
|
140
|
+
- position_ratio
|
|
141
|
+
- position_in_grid
|
|
142
|
+
- margins_mm, margins_inch
|
|
143
|
+
- bbox_mm, bbox_inch, bbox_px
|
|
144
|
+
- x_axis_bottom, y_axis_left (axis info)
|
|
145
|
+
"""
|
|
146
|
+
ax_metadata = {}
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
from ._figure_from_axes_mm import get_dimension_info
|
|
150
|
+
|
|
151
|
+
dim_info = get_dimension_info(fig, ax)
|
|
152
|
+
|
|
153
|
+
# Size in multiple units
|
|
154
|
+
ax_metadata["size_mm"] = dim_info.get("axes_size_mm", [])
|
|
155
|
+
if "axes_size_inch" in dim_info:
|
|
156
|
+
ax_metadata["size_inch"] = dim_info["axes_size_inch"]
|
|
157
|
+
if "axes_size_px" in dim_info:
|
|
158
|
+
ax_metadata["size_px"] = dim_info["axes_size_px"]
|
|
159
|
+
|
|
160
|
+
# Position in figure coordinates (normalized 0-1 values)
|
|
161
|
+
# Uses matplotlib terminology: bounds_figure_fraction
|
|
162
|
+
if "axes_position" in dim_info:
|
|
163
|
+
ax_metadata["bounds_figure_fraction"] = list(dim_info["axes_position"])
|
|
164
|
+
|
|
165
|
+
# Position in grid (row, col)
|
|
166
|
+
if hasattr(ax, "_scitex_metadata") and "position_in_grid" in ax._scitex_metadata:
|
|
167
|
+
ax_metadata["position_in_grid"] = ax._scitex_metadata["position_in_grid"]
|
|
168
|
+
else:
|
|
169
|
+
# Calculate from ax_index if we have grid info
|
|
170
|
+
ax_metadata["position_in_grid"] = [ax_index, 0] # Default single column
|
|
171
|
+
|
|
172
|
+
# Margins in mm and inch
|
|
173
|
+
if "margins_mm" in dim_info:
|
|
174
|
+
ax_metadata["margins_mm"] = dim_info["margins_mm"]
|
|
175
|
+
if "margins_inch" in dim_info:
|
|
176
|
+
ax_metadata["margins_inch"] = dim_info["margins_inch"]
|
|
177
|
+
|
|
178
|
+
# Bounding box with intuitive keys
|
|
179
|
+
if "axes_bbox_px" in dim_info:
|
|
180
|
+
bbox = dim_info["axes_bbox_px"]
|
|
181
|
+
# Convert from x0/y0/x1/y1 to x_left/y_bottom/x_right/y_top
|
|
182
|
+
ax_metadata["bbox_px"] = {
|
|
183
|
+
"x_left": bbox.get("x0", bbox.get("x_left", 0)),
|
|
184
|
+
"x_right": bbox.get("x1", bbox.get("x_right", 0)),
|
|
185
|
+
"y_top": bbox.get("y0", bbox.get("y_top", 0)),
|
|
186
|
+
"y_bottom": bbox.get("y1", bbox.get("y_bottom", 0)),
|
|
187
|
+
"width": bbox.get("width", 0),
|
|
188
|
+
"height": bbox.get("height", 0),
|
|
189
|
+
}
|
|
190
|
+
if "axes_bbox_mm" in dim_info:
|
|
191
|
+
bbox = dim_info["axes_bbox_mm"]
|
|
192
|
+
ax_metadata["bbox_mm"] = {
|
|
193
|
+
"x_left": bbox.get("x0", bbox.get("x_left", 0)),
|
|
194
|
+
"x_right": bbox.get("x1", bbox.get("x_right", 0)),
|
|
195
|
+
"y_top": bbox.get("y0", bbox.get("y_top", 0)),
|
|
196
|
+
"y_bottom": bbox.get("y1", bbox.get("y_bottom", 0)),
|
|
197
|
+
"width": bbox.get("width", 0),
|
|
198
|
+
"height": bbox.get("height", 0),
|
|
199
|
+
}
|
|
200
|
+
if "axes_bbox_inch" in dim_info:
|
|
201
|
+
bbox = dim_info["axes_bbox_inch"]
|
|
202
|
+
ax_metadata["bbox_inch"] = {
|
|
203
|
+
"x_left": bbox.get("x0", bbox.get("x_left", 0)),
|
|
204
|
+
"x_right": bbox.get("x1", bbox.get("x_right", 0)),
|
|
205
|
+
"y_top": bbox.get("y0", bbox.get("y_top", 0)),
|
|
206
|
+
"y_bottom": bbox.get("y1", bbox.get("y_bottom", 0)),
|
|
207
|
+
"width": bbox.get("width", 0),
|
|
208
|
+
"height": bbox.get("height", 0),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.warning(f"Could not extract dimension info for axes {ax_index}: {e}")
|
|
213
|
+
|
|
214
|
+
# Extract axes labels and units
|
|
215
|
+
# X-axis - using matplotlib terminology (xaxis)
|
|
216
|
+
xlabel = ax.get_xlabel()
|
|
217
|
+
x_label, x_unit = _parse_label_unit(xlabel)
|
|
218
|
+
ax_metadata["xaxis"] = {
|
|
219
|
+
"label": x_label,
|
|
220
|
+
"unit": x_unit,
|
|
221
|
+
"scale": ax.get_xscale(),
|
|
222
|
+
"lim": list(ax.get_xlim()),
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Y-axis - using matplotlib terminology (yaxis)
|
|
226
|
+
ylabel = ax.get_ylabel()
|
|
227
|
+
y_label, y_unit = _parse_label_unit(ylabel)
|
|
228
|
+
ax_metadata["yaxis"] = {
|
|
229
|
+
"label": y_label,
|
|
230
|
+
"unit": y_unit,
|
|
231
|
+
"scale": ax.get_yscale(),
|
|
232
|
+
"lim": list(ax.get_ylim()),
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return ax_metadata
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _restructure_style(flat_style: dict) -> dict:
|
|
239
|
+
"""
|
|
240
|
+
Restructure flat style_mm dict into hierarchical structure with explicit scopes.
|
|
241
|
+
|
|
242
|
+
Converts:
|
|
243
|
+
{"axis_thickness_mm": 0.2, "tick_length_mm": 0.8, ...}
|
|
244
|
+
To:
|
|
245
|
+
{
|
|
246
|
+
"global": {"fonts": {...}, "padding": {...}},
|
|
247
|
+
"axes_default": {"axes": {...}, "ticks": {...}},
|
|
248
|
+
"artist_default": {"lines": {...}, "markers": {...}}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Style scopes:
|
|
252
|
+
- global: rcParams-like settings (fonts, padding) applied to entire figure
|
|
253
|
+
- axes_default: default axes appearance (can be overridden per-axes)
|
|
254
|
+
- artist_default: default artist appearance (can be overridden per-artist)
|
|
255
|
+
"""
|
|
256
|
+
result = {
|
|
257
|
+
"global": {
|
|
258
|
+
"fonts": {},
|
|
259
|
+
"padding": {},
|
|
260
|
+
},
|
|
261
|
+
"axes_default": {
|
|
262
|
+
"axes": {},
|
|
263
|
+
"ticks": {},
|
|
264
|
+
},
|
|
265
|
+
"artist_default": {
|
|
266
|
+
"lines": {},
|
|
267
|
+
"markers": {},
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# Mapping from flat keys to hierarchical structure (scope, category, key)
|
|
272
|
+
key_mapping = {
|
|
273
|
+
# Axes-level defaults
|
|
274
|
+
"axis_thickness_mm": ("axes_default", "axes", "thickness_mm"),
|
|
275
|
+
"axes_thickness_mm": ("axes_default", "axes", "thickness_mm"),
|
|
276
|
+
"tick_length_mm": ("axes_default", "ticks", "length_mm"),
|
|
277
|
+
"tick_thickness_mm": ("axes_default", "ticks", "thickness_mm"),
|
|
278
|
+
"n_ticks": ("axes_default", "ticks", "n_ticks"),
|
|
279
|
+
# Artist-level defaults (Line2D, markers)
|
|
280
|
+
"trace_thickness_mm": ("artist_default", "lines", "thickness_mm"),
|
|
281
|
+
"line_thickness_mm": ("artist_default", "lines", "thickness_mm"),
|
|
282
|
+
"marker_size_mm": ("artist_default", "markers", "size_mm"),
|
|
283
|
+
"scatter_size_mm": ("artist_default", "markers", "scatter_size_mm"),
|
|
284
|
+
# Global defaults (rcParams-like)
|
|
285
|
+
"font_family": ("global", "fonts", "family"),
|
|
286
|
+
"font_family_requested": ("global", "fonts", "family_requested"),
|
|
287
|
+
"font_family_actual": ("global", "fonts", "family_actual"),
|
|
288
|
+
"axis_font_size_pt": ("global", "fonts", "axis_size_pt"),
|
|
289
|
+
"tick_font_size_pt": ("global", "fonts", "tick_size_pt"),
|
|
290
|
+
"title_font_size_pt": ("global", "fonts", "title_size_pt"),
|
|
291
|
+
"legend_font_size_pt": ("global", "fonts", "legend_size_pt"),
|
|
292
|
+
"suptitle_font_size_pt": ("global", "fonts", "suptitle_size_pt"),
|
|
293
|
+
"annotation_font_size_pt": ("global", "fonts", "annotation_size_pt"),
|
|
294
|
+
"label_pad_pt": ("global", "padding", "label_pt"),
|
|
295
|
+
"tick_pad_pt": ("global", "padding", "tick_pt"),
|
|
296
|
+
"title_pad_pt": ("global", "padding", "title_pt"),
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for key, value in flat_style.items():
|
|
300
|
+
if key in key_mapping:
|
|
301
|
+
scope, category, new_key = key_mapping[key]
|
|
302
|
+
result[scope][category][new_key] = value
|
|
303
|
+
else:
|
|
304
|
+
# Unknown keys go to a misc section or are kept at top level
|
|
305
|
+
# For now, skip unknown keys to keep structure clean
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
# Remove empty categories within each scope
|
|
309
|
+
for scope in list(result.keys()):
|
|
310
|
+
result[scope] = {k: v for k, v in result[scope].items() if v}
|
|
311
|
+
# Remove empty scopes
|
|
312
|
+
if not result[scope]:
|
|
313
|
+
del result[scope]
|
|
314
|
+
|
|
315
|
+
return result
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def collect_figure_metadata(fig, ax=None) -> Dict:
|
|
319
|
+
"""
|
|
320
|
+
Collect all metadata from figure and axes for embedding in saved images.
|
|
321
|
+
|
|
322
|
+
This function automatically extracts:
|
|
323
|
+
- Software versions (scitex, matplotlib)
|
|
324
|
+
- Timestamp
|
|
325
|
+
- Figure UUID (unique identifier)
|
|
326
|
+
- Figure/axes dimensions (mm, inch, px)
|
|
327
|
+
- DPI settings
|
|
328
|
+
- Margins
|
|
329
|
+
- Styling parameters (if available)
|
|
330
|
+
- Mode (display/publication)
|
|
331
|
+
- Creation method
|
|
332
|
+
- Plot type and axes information
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
fig : matplotlib.figure.Figure
|
|
337
|
+
Figure to collect metadata from
|
|
338
|
+
ax : matplotlib.axes.Axes, optional
|
|
339
|
+
Primary axes to collect dimension info from.
|
|
340
|
+
If not provided, uses first axes in figure.
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
dict
|
|
345
|
+
Complete metadata dictionary ready for embedding via scitex.io.embed_metadata()
|
|
346
|
+
|
|
347
|
+
Examples
|
|
348
|
+
--------
|
|
349
|
+
>>> from scitex.plt.utils import create_axes_with_size_mm, collect_figure_metadata
|
|
350
|
+
>>> fig, ax = create_axes_with_size_mm(30, 21, mode='publication')
|
|
351
|
+
>>> ax.plot(x, y)
|
|
352
|
+
>>> metadata = collect_figure_metadata(fig, ax)
|
|
353
|
+
>>> print(metadata['dimensions']['axes_size_mm'])
|
|
354
|
+
(30.0, 21.0)
|
|
355
|
+
|
|
356
|
+
Notes
|
|
357
|
+
-----
|
|
358
|
+
This function is automatically called by FigWrapper.savefig() when
|
|
359
|
+
embed_metadata=True (the default). You typically don't need to call it manually.
|
|
360
|
+
|
|
361
|
+
The collected metadata enables:
|
|
362
|
+
- Reproducing exact figure dimensions later
|
|
363
|
+
- Matching styling across multiple figures
|
|
364
|
+
- Documenting figure provenance
|
|
365
|
+
- Debugging dimension/DPI issues
|
|
366
|
+
"""
|
|
367
|
+
import datetime
|
|
368
|
+
import uuid
|
|
369
|
+
|
|
370
|
+
import matplotlib
|
|
371
|
+
import scitex
|
|
372
|
+
|
|
373
|
+
# Base metadata with cleaner structure:
|
|
374
|
+
# - runtime: software/creation info
|
|
375
|
+
# - figure: figure-level properties
|
|
376
|
+
# - axes: axes-level properties
|
|
377
|
+
# - style: styling parameters
|
|
378
|
+
# - plot: plot content (title, type, traces, legend)
|
|
379
|
+
# - data: CSV linkage (path, hash, columns)
|
|
380
|
+
metadata = {
|
|
381
|
+
"scitex_schema": "scitex.plt.figure",
|
|
382
|
+
"scitex_schema_version": "0.1.0",
|
|
383
|
+
"figure_uuid": str(uuid.uuid4()),
|
|
384
|
+
"runtime": {
|
|
385
|
+
"scitex_version": scitex.__version__,
|
|
386
|
+
"matplotlib_version": matplotlib.__version__,
|
|
387
|
+
"created_at": datetime.datetime.now().isoformat(),
|
|
388
|
+
},
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
# Collect all axes from figure
|
|
392
|
+
# Keep AxisWrappers for metadata access, but also track grid shape
|
|
393
|
+
all_axes = [] # List of (ax_wrapper_or_mpl, row, col) tuples
|
|
394
|
+
grid_shape = (1, 1) # Default single axes
|
|
395
|
+
|
|
396
|
+
if ax is not None:
|
|
397
|
+
# Handle AxesWrapper (multi-axes) - extract individual AxisWrappers with positions
|
|
398
|
+
if hasattr(ax, "_axes_scitex"):
|
|
399
|
+
import numpy as np
|
|
400
|
+
axes_array = ax._axes_scitex
|
|
401
|
+
if isinstance(axes_array, np.ndarray):
|
|
402
|
+
grid_shape = axes_array.shape
|
|
403
|
+
for idx, ax_item in enumerate(axes_array.flat):
|
|
404
|
+
row = idx // grid_shape[1]
|
|
405
|
+
col = idx % grid_shape[1]
|
|
406
|
+
all_axes.append((ax_item, row, col))
|
|
407
|
+
else:
|
|
408
|
+
all_axes = [(axes_array, 0, 0)]
|
|
409
|
+
# Handle AxisWrapper (single axes)
|
|
410
|
+
elif hasattr(ax, "_axis_mpl"):
|
|
411
|
+
all_axes = [(ax, 0, 0)]
|
|
412
|
+
else:
|
|
413
|
+
# Assume it's a matplotlib axes
|
|
414
|
+
all_axes = [(ax, 0, 0)]
|
|
415
|
+
elif hasattr(fig, "axes") and len(fig.axes) > 0:
|
|
416
|
+
# Fallback to figure axes (linear indexing)
|
|
417
|
+
for idx, ax_item in enumerate(fig.axes):
|
|
418
|
+
all_axes.append((ax_item, 0, idx))
|
|
419
|
+
|
|
420
|
+
# Helper to unwrap AxisWrapper to matplotlib axes
|
|
421
|
+
def _unwrap_ax(ax_item):
|
|
422
|
+
if hasattr(ax_item, "_axis_mpl"):
|
|
423
|
+
return ax_item._axis_mpl
|
|
424
|
+
return ax_item
|
|
425
|
+
|
|
426
|
+
# Add figure-level properties (extracted from first axes for figure dimensions)
|
|
427
|
+
if all_axes:
|
|
428
|
+
try:
|
|
429
|
+
from ._figure_from_axes_mm import get_dimension_info
|
|
430
|
+
|
|
431
|
+
first_ax_tuple = all_axes[0]
|
|
432
|
+
first_ax_mpl = _unwrap_ax(first_ax_tuple[0])
|
|
433
|
+
dim_info = get_dimension_info(fig, first_ax_mpl)
|
|
434
|
+
|
|
435
|
+
metadata["figure"] = {
|
|
436
|
+
"size_mm": dim_info["figure_size_mm"],
|
|
437
|
+
"size_inch": dim_info["figure_size_inch"],
|
|
438
|
+
"size_px": dim_info["figure_size_px"],
|
|
439
|
+
"dpi": dim_info["dpi"],
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
# Add top-level axes_bbox_px for easy access by canvas/web editors
|
|
443
|
+
# Uses x0/y0/x1/y1 format (origin at top-left for web compatibility)
|
|
444
|
+
# x0: left edge (Y-axis position), y1: bottom edge (X-axis position)
|
|
445
|
+
if "axes_bbox_px" in dim_info:
|
|
446
|
+
metadata["axes_bbox_px"] = dim_info["axes_bbox_px"]
|
|
447
|
+
if "axes_bbox_mm" in dim_info:
|
|
448
|
+
metadata["axes_bbox_mm"] = dim_info["axes_bbox_mm"]
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.warning(f"Could not extract figure dimension info: {e}")
|
|
451
|
+
|
|
452
|
+
# Collect per-axes metadata
|
|
453
|
+
if all_axes:
|
|
454
|
+
metadata["axes"] = {}
|
|
455
|
+
for ax_item, row, col in all_axes:
|
|
456
|
+
# Use row-col format: ax_00, ax_01, ax_10, ax_11 for 2x2 grid
|
|
457
|
+
ax_key = f"ax_{row}{col}"
|
|
458
|
+
try:
|
|
459
|
+
ax_mpl = _unwrap_ax(ax_item)
|
|
460
|
+
ax_metadata = _collect_single_axes_metadata(fig, ax_mpl, row * grid_shape[1] + col)
|
|
461
|
+
if ax_metadata:
|
|
462
|
+
# Add grid position info
|
|
463
|
+
ax_metadata["grid_position"] = {"row": row, "col": col}
|
|
464
|
+
metadata["axes"][ax_key] = ax_metadata
|
|
465
|
+
except Exception as e:
|
|
466
|
+
logger.warning(f"Could not extract metadata for {ax_key}: {e}")
|
|
467
|
+
|
|
468
|
+
# Add scitex-specific metadata if axes was tagged
|
|
469
|
+
scitex_meta = None
|
|
470
|
+
if ax is not None and hasattr(ax, "_scitex_metadata"):
|
|
471
|
+
scitex_meta = ax._scitex_metadata
|
|
472
|
+
elif hasattr(fig, "_scitex_metadata"):
|
|
473
|
+
scitex_meta = fig._scitex_metadata
|
|
474
|
+
|
|
475
|
+
if scitex_meta:
|
|
476
|
+
# Extract stats separately for top-level access
|
|
477
|
+
if "stats" in scitex_meta:
|
|
478
|
+
stats_list = scitex_meta["stats"]
|
|
479
|
+
# Determine first_ax_key from axes metadata
|
|
480
|
+
first_ax_key = None
|
|
481
|
+
if "axes" in metadata and metadata["axes"]:
|
|
482
|
+
first_ax_key = next(iter(metadata["axes"].keys()), None)
|
|
483
|
+
# Add plot_id and ax_id to each stats entry if not present
|
|
484
|
+
for stat in stats_list:
|
|
485
|
+
if isinstance(stat, dict):
|
|
486
|
+
# Try to get plot info from metadata
|
|
487
|
+
if stat.get("plot_id") is None:
|
|
488
|
+
if "plot" in metadata and "ax_id" in metadata["plot"]:
|
|
489
|
+
stat["plot_id"] = metadata["plot"]["ax_id"]
|
|
490
|
+
elif first_ax_key:
|
|
491
|
+
stat["plot_id"] = first_ax_key
|
|
492
|
+
if "ax_id" not in stat and first_ax_key:
|
|
493
|
+
stat["ax_id"] = first_ax_key
|
|
494
|
+
metadata["stats"] = stats_list
|
|
495
|
+
|
|
496
|
+
# Extract style_mm to dedicated "style" section with hierarchical structure
|
|
497
|
+
if "style_mm" in scitex_meta:
|
|
498
|
+
metadata["style"] = _restructure_style(scitex_meta["style_mm"])
|
|
499
|
+
|
|
500
|
+
# Extract mode to figure section
|
|
501
|
+
if "mode" in scitex_meta:
|
|
502
|
+
if "figure" not in metadata:
|
|
503
|
+
metadata["figure"] = {}
|
|
504
|
+
metadata["figure"]["mode"] = scitex_meta["mode"]
|
|
505
|
+
|
|
506
|
+
# Extract created_with to runtime section
|
|
507
|
+
if "created_with" in scitex_meta:
|
|
508
|
+
metadata["runtime"]["created_with"] = scitex_meta["created_with"]
|
|
509
|
+
|
|
510
|
+
# Note: axes_size_mm and position_in_grid are now handled per-axes
|
|
511
|
+
# in _collect_single_axes_metadata() and stored under axes.ax_00, axes.ax_01, etc.
|
|
512
|
+
|
|
513
|
+
# Add actual font information
|
|
514
|
+
try:
|
|
515
|
+
from ._get_actual_font import get_actual_font_name
|
|
516
|
+
|
|
517
|
+
actual_font = get_actual_font_name()
|
|
518
|
+
|
|
519
|
+
# Store both requested and actual font in style.global.fonts section
|
|
520
|
+
if "style" in metadata:
|
|
521
|
+
# Ensure global.fonts section exists
|
|
522
|
+
if "global" not in metadata["style"]:
|
|
523
|
+
metadata["style"]["global"] = {}
|
|
524
|
+
if "fonts" not in metadata["style"]["global"]:
|
|
525
|
+
metadata["style"]["global"]["fonts"] = {}
|
|
526
|
+
|
|
527
|
+
# Get requested font from global.fonts.family or default to Arial
|
|
528
|
+
requested_font = metadata["style"]["global"]["fonts"].get("family", "Arial")
|
|
529
|
+
# Remove redundant family - keep only family_requested and family_actual
|
|
530
|
+
if "family" in metadata["style"]["global"]["fonts"]:
|
|
531
|
+
del metadata["style"]["global"]["fonts"]["family"]
|
|
532
|
+
metadata["style"]["global"]["fonts"]["family_requested"] = requested_font
|
|
533
|
+
metadata["style"]["global"]["fonts"]["family_actual"] = actual_font
|
|
534
|
+
|
|
535
|
+
# Warn if requested and actual fonts differ
|
|
536
|
+
if requested_font != actual_font:
|
|
537
|
+
try:
|
|
538
|
+
from scitex.logging import getLogger
|
|
539
|
+
|
|
540
|
+
logger = getLogger(__name__)
|
|
541
|
+
logger.warning(
|
|
542
|
+
f"Font mismatch: Requested '{requested_font}' but using '{actual_font}'. "
|
|
543
|
+
f"For {requested_font}: sudo apt-get install ttf-mscorefonts-installer && fc-cache -fv"
|
|
544
|
+
)
|
|
545
|
+
except ImportError:
|
|
546
|
+
logger.warning(
|
|
547
|
+
f"Font mismatch: Requested '{requested_font}' but using '{actual_font}'"
|
|
548
|
+
)
|
|
549
|
+
else:
|
|
550
|
+
# If no style section, add font info to runtime section
|
|
551
|
+
metadata["runtime"]["font_family_actual"] = actual_font
|
|
552
|
+
except Exception:
|
|
553
|
+
# If font detection fails, continue without it
|
|
554
|
+
pass
|
|
555
|
+
|
|
556
|
+
# Extract plot content and axes labels
|
|
557
|
+
# For multi-axes figures, we need to handle AxesWrapper specially
|
|
558
|
+
primary_ax = ax
|
|
559
|
+
if ax is not None:
|
|
560
|
+
# Handle AxesWrapper (multi-axes) - use first axis for primary plot info
|
|
561
|
+
if hasattr(ax, "_axes_scitex"):
|
|
562
|
+
import numpy as np
|
|
563
|
+
axes_array = ax._axes_scitex
|
|
564
|
+
if isinstance(axes_array, np.ndarray) and axes_array.size > 0:
|
|
565
|
+
primary_ax = axes_array.flat[0]
|
|
566
|
+
else:
|
|
567
|
+
primary_ax = axes_array
|
|
568
|
+
|
|
569
|
+
if primary_ax is not None:
|
|
570
|
+
try:
|
|
571
|
+
# Try to get scitex AxisWrapper for history access
|
|
572
|
+
# This is needed because matplotlib axes don't have the tracking history
|
|
573
|
+
ax_for_history = primary_ax
|
|
574
|
+
|
|
575
|
+
# If ax is a raw matplotlib axes, try to find the scitex wrapper
|
|
576
|
+
if not hasattr(primary_ax, 'history'):
|
|
577
|
+
# Check if primary_ax has a scitex wrapper stored on it
|
|
578
|
+
if hasattr(primary_ax, '_scitex_wrapper'):
|
|
579
|
+
ax_for_history = primary_ax._scitex_wrapper
|
|
580
|
+
# Check if figure has scitex axes reference
|
|
581
|
+
elif hasattr(fig, 'axes') and hasattr(fig.axes, 'history'):
|
|
582
|
+
ax_for_history = fig.axes
|
|
583
|
+
# Check for FigWrapper's axes attribute
|
|
584
|
+
elif hasattr(fig, '_fig_scitex') and hasattr(fig._fig_scitex, 'axes'):
|
|
585
|
+
ax_for_history = fig._fig_scitex.axes
|
|
586
|
+
# Check if the figure object itself has scitex_axes
|
|
587
|
+
elif hasattr(fig, '_scitex_axes'):
|
|
588
|
+
ax_for_history = fig._scitex_axes
|
|
589
|
+
|
|
590
|
+
# Add n_ticks to axes metadata if available from style
|
|
591
|
+
if "style" in metadata and "ticks" in metadata["style"] and "n_ticks" in metadata["style"]["ticks"]:
|
|
592
|
+
n_ticks = metadata["style"]["ticks"]["n_ticks"]
|
|
593
|
+
# Add n_ticks to each axes' axis info
|
|
594
|
+
if "axes" in metadata:
|
|
595
|
+
for ax_key in metadata["axes"]:
|
|
596
|
+
ax_data = metadata["axes"][ax_key]
|
|
597
|
+
if "xaxis" in ax_data:
|
|
598
|
+
ax_data["xaxis"]["n_ticks"] = n_ticks
|
|
599
|
+
if "yaxis" in ax_data:
|
|
600
|
+
ax_data["yaxis"]["n_ticks"] = n_ticks
|
|
601
|
+
|
|
602
|
+
# Initialize plot section for plot content
|
|
603
|
+
plot_info = {}
|
|
604
|
+
|
|
605
|
+
# Add ax_id to match the axes key in metadata["axes"]
|
|
606
|
+
# This links plot info to the corresponding axes entry
|
|
607
|
+
ax_row, ax_col = 0, 0 # Default for single axes
|
|
608
|
+
if hasattr(primary_ax, "_scitex_metadata") and "position_in_grid" in primary_ax._scitex_metadata:
|
|
609
|
+
pos = primary_ax._scitex_metadata["position_in_grid"]
|
|
610
|
+
ax_row, ax_col = pos[0], pos[1]
|
|
611
|
+
# Use same format as axes keys: ax_00, ax_01, etc.
|
|
612
|
+
plot_info["ax_id"] = f"ax_{ax_row:02d}" if ax_row == ax_col == 0 else f"ax_{ax_row * 10 + ax_col:02d}"
|
|
613
|
+
|
|
614
|
+
# Extract title - use underlying matplotlib axes if needed
|
|
615
|
+
ax_mpl = primary_ax._axis_mpl if hasattr(primary_ax, '_axis_mpl') else primary_ax
|
|
616
|
+
title = ax_mpl.get_title()
|
|
617
|
+
if title:
|
|
618
|
+
plot_info["title"] = title
|
|
619
|
+
|
|
620
|
+
# Detect plot type and method from axes history or lines
|
|
621
|
+
# Use ax_for_history which has the scitex history if available
|
|
622
|
+
plot_type, method = _detect_plot_type(ax_for_history)
|
|
623
|
+
if plot_type:
|
|
624
|
+
plot_info["type"] = plot_type
|
|
625
|
+
if method:
|
|
626
|
+
plot_info["method"] = method
|
|
627
|
+
|
|
628
|
+
# Extract style preset if available
|
|
629
|
+
if (
|
|
630
|
+
hasattr(primary_ax, "_scitex_metadata")
|
|
631
|
+
and "style_preset" in primary_ax._scitex_metadata
|
|
632
|
+
):
|
|
633
|
+
if "style" not in metadata:
|
|
634
|
+
metadata["style"] = {}
|
|
635
|
+
metadata["style"]["preset"] = primary_ax._scitex_metadata["style_preset"]
|
|
636
|
+
elif (
|
|
637
|
+
hasattr(fig, "_scitex_metadata")
|
|
638
|
+
and "style_preset" in fig._scitex_metadata
|
|
639
|
+
):
|
|
640
|
+
if "style" not in metadata:
|
|
641
|
+
metadata["style"] = {}
|
|
642
|
+
metadata["style"]["preset"] = fig._scitex_metadata["style_preset"]
|
|
643
|
+
|
|
644
|
+
# Extract artists and legend - add to axes section (matplotlib terminology)
|
|
645
|
+
# Artists and legend belong to axes, not a separate plot section
|
|
646
|
+
ax_row, ax_col = 0, 0
|
|
647
|
+
if hasattr(primary_ax, "_scitex_metadata") and "position_in_grid" in primary_ax._scitex_metadata:
|
|
648
|
+
pos = primary_ax._scitex_metadata["position_in_grid"]
|
|
649
|
+
ax_row, ax_col = pos[0], pos[1]
|
|
650
|
+
ax_key = f"ax_{ax_row:02d}" if ax_row == ax_col == 0 else f"ax_{ax_row * 10 + ax_col:02d}"
|
|
651
|
+
|
|
652
|
+
if "axes" in metadata and ax_key in metadata["axes"]:
|
|
653
|
+
# Add artists to axes
|
|
654
|
+
artists = _extract_artists(primary_ax)
|
|
655
|
+
if artists:
|
|
656
|
+
metadata["axes"][ax_key]["artists"] = artists
|
|
657
|
+
|
|
658
|
+
# Add legend to axes
|
|
659
|
+
legend_info = _extract_legend_info(primary_ax)
|
|
660
|
+
if legend_info:
|
|
661
|
+
metadata["axes"][ax_key]["legend"] = legend_info
|
|
662
|
+
|
|
663
|
+
# Add plot section if we have content
|
|
664
|
+
if plot_info:
|
|
665
|
+
metadata["plot"] = plot_info
|
|
666
|
+
|
|
667
|
+
# Data section for CSV linkage
|
|
668
|
+
# Note: Per-trace column mappings are in plot.traces[i].csv_columns
|
|
669
|
+
# This section provides:
|
|
670
|
+
# - csv_hash: for verifying data integrity
|
|
671
|
+
# - csv_path: path to CSV file (added by _save.py)
|
|
672
|
+
# - columns_actual: actual column names in CSV (added by _save.py after export)
|
|
673
|
+
data_info = {}
|
|
674
|
+
|
|
675
|
+
# Compute CSV data hash for reproducibility verification
|
|
676
|
+
csv_hash = _compute_csv_hash(ax_for_history)
|
|
677
|
+
if csv_hash:
|
|
678
|
+
data_info["csv_hash"] = csv_hash
|
|
679
|
+
|
|
680
|
+
# csv_path and columns_actual will be added by _save.py after actual CSV export
|
|
681
|
+
# This ensures single source of truth - actual columns, not predictions
|
|
682
|
+
|
|
683
|
+
# Add data section if we have content
|
|
684
|
+
if data_info:
|
|
685
|
+
metadata["data"] = data_info
|
|
686
|
+
|
|
687
|
+
except Exception as e:
|
|
688
|
+
# If Phase 1 extraction fails, continue without it
|
|
689
|
+
logger.warning(f"Could not extract Phase 1 metadata: {e}")
|
|
690
|
+
|
|
691
|
+
# Apply precision rounding to all numeric values
|
|
692
|
+
metadata = _round_metadata(metadata)
|
|
693
|
+
|
|
694
|
+
return metadata
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _round_metadata(metadata: dict) -> dict:
|
|
698
|
+
"""
|
|
699
|
+
Apply appropriate precision rounding to all numeric values in metadata.
|
|
700
|
+
|
|
701
|
+
Precision rules:
|
|
702
|
+
- mm values: 2 decimal places (0.01mm = 10 microns)
|
|
703
|
+
- inch values: 3 decimal places
|
|
704
|
+
- position values: 3 decimal places
|
|
705
|
+
- axis limits: 2 decimal places
|
|
706
|
+
- linewidth: 2 decimal places
|
|
707
|
+
- px values: integers (no decimals)
|
|
708
|
+
"""
|
|
709
|
+
result = {}
|
|
710
|
+
|
|
711
|
+
for key, value in metadata.items():
|
|
712
|
+
if key in ("scitex_schema", "scitex_schema_version", "figure_uuid"):
|
|
713
|
+
# String fields - no rounding
|
|
714
|
+
result[key] = value
|
|
715
|
+
elif key == "runtime":
|
|
716
|
+
# Runtime section - no numeric values to round
|
|
717
|
+
result[key] = value
|
|
718
|
+
elif key == "figure":
|
|
719
|
+
result[key] = _round_figure_section(value)
|
|
720
|
+
elif key == "axes":
|
|
721
|
+
result[key] = _round_axes_section(value)
|
|
722
|
+
elif key == "style":
|
|
723
|
+
result[key] = _round_style_section(value)
|
|
724
|
+
elif key == "plot":
|
|
725
|
+
result[key] = _round_plot_section(value)
|
|
726
|
+
elif key == "data":
|
|
727
|
+
# Data section - no numeric values to round (hashes, paths, column names)
|
|
728
|
+
result[key] = value
|
|
729
|
+
elif key == "stats":
|
|
730
|
+
# Stats section - preserve precision for statistical values
|
|
731
|
+
result[key] = value
|
|
732
|
+
else:
|
|
733
|
+
result[key] = value
|
|
734
|
+
|
|
735
|
+
return result
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _round_figure_section(fig_data: dict) -> dict:
|
|
739
|
+
"""Round values in figure section."""
|
|
740
|
+
result = {}
|
|
741
|
+
for key, value in fig_data.items():
|
|
742
|
+
if key == "size_mm":
|
|
743
|
+
# Fixed 2 decimals for mm: [80.00, 68.00]
|
|
744
|
+
result[key] = _round_list(value, PRECISION["mm"], fixed=True)
|
|
745
|
+
elif key == "size_inch":
|
|
746
|
+
# Fixed 3 decimals for inch: [3.150, 2.677]
|
|
747
|
+
result[key] = _round_list(value, PRECISION["inch"], fixed=True)
|
|
748
|
+
elif key == "size_px":
|
|
749
|
+
result[key] = [int(v) for v in value] # Pixels are integers
|
|
750
|
+
elif key == "dpi":
|
|
751
|
+
result[key] = int(value)
|
|
752
|
+
else:
|
|
753
|
+
result[key] = value
|
|
754
|
+
return result
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def _round_axes_section(axes_data: dict) -> dict:
|
|
758
|
+
"""Round values in axes section.
|
|
759
|
+
|
|
760
|
+
Handles both flat structure (legacy) and nested structure (ax_00, ax_01, ...).
|
|
761
|
+
"""
|
|
762
|
+
result = {}
|
|
763
|
+
for key, value in axes_data.items():
|
|
764
|
+
# Check if this is a nested axes key (ax_00, ax_01, etc.)
|
|
765
|
+
if key.startswith("ax_") and isinstance(value, dict):
|
|
766
|
+
# Recursively round the nested axes data
|
|
767
|
+
result[key] = _round_single_axes_data(value)
|
|
768
|
+
else:
|
|
769
|
+
# Handle flat structure (legacy) or non-axes keys
|
|
770
|
+
result[key] = _round_single_axes_data({key: value}).get(key, value)
|
|
771
|
+
return result
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def _round_single_axes_data(ax_data: dict) -> dict:
|
|
775
|
+
"""Round values for a single axes' data."""
|
|
776
|
+
result = {}
|
|
777
|
+
for key, value in ax_data.items():
|
|
778
|
+
if key == "size_mm":
|
|
779
|
+
# Fixed 2 decimals: [40.00, 28.00]
|
|
780
|
+
result[key] = _round_list(value, PRECISION["mm"], fixed=True)
|
|
781
|
+
elif key == "size_inch":
|
|
782
|
+
# Fixed 3 decimals: [1.575, 1.102]
|
|
783
|
+
result[key] = _round_list(value, PRECISION["inch"], fixed=True)
|
|
784
|
+
elif key == "size_px":
|
|
785
|
+
result[key] = [int(v) for v in value]
|
|
786
|
+
elif key in ("position", "position_ratio", "bounds_figure_fraction"):
|
|
787
|
+
# Fixed 3 decimals: [0.250, 0.294, 0.500, 0.412]
|
|
788
|
+
result[key] = _round_list(value, PRECISION["position"], fixed=True)
|
|
789
|
+
elif key == "position_in_grid":
|
|
790
|
+
result[key] = [int(v) for v in value]
|
|
791
|
+
elif key == "margins_mm":
|
|
792
|
+
# Fixed 2 decimals: {"left": 20.00, ...}
|
|
793
|
+
result[key] = {k: _round_value(v, PRECISION["mm"], fixed=True) for k, v in value.items()}
|
|
794
|
+
elif key == "margins_inch":
|
|
795
|
+
# Fixed 3 decimals: {"left": 0.787, ...}
|
|
796
|
+
result[key] = {k: _round_value(v, PRECISION["inch"], fixed=True) for k, v in value.items()}
|
|
797
|
+
elif key == "bbox_mm":
|
|
798
|
+
# Fixed 2 decimals
|
|
799
|
+
result[key] = {k: _round_value(v, PRECISION["mm"], fixed=True) for k, v in value.items()}
|
|
800
|
+
elif key == "bbox_inch":
|
|
801
|
+
# Fixed 3 decimals
|
|
802
|
+
result[key] = {k: _round_value(v, PRECISION["inch"], fixed=True) for k, v in value.items()}
|
|
803
|
+
elif key == "bbox_px":
|
|
804
|
+
result[key] = {k: int(v) for k, v in value.items()}
|
|
805
|
+
elif key in ("xaxis", "yaxis", "xaxis_top", "yaxis_right"):
|
|
806
|
+
# Axis info (label, unit, scale, lim, n_ticks) - using matplotlib terminology
|
|
807
|
+
axis_result = {}
|
|
808
|
+
for ak, av in value.items():
|
|
809
|
+
if ak == "lim":
|
|
810
|
+
# Fixed 2 decimals for limits: [-0.31, 6.60]
|
|
811
|
+
axis_result[ak] = _round_list(av, PRECISION["lim"], fixed=True)
|
|
812
|
+
elif ak == "n_ticks":
|
|
813
|
+
axis_result[ak] = int(av)
|
|
814
|
+
else:
|
|
815
|
+
axis_result[ak] = av
|
|
816
|
+
result[key] = axis_result
|
|
817
|
+
elif key == "legend":
|
|
818
|
+
# Legend has no floats to round, pass through
|
|
819
|
+
result[key] = value
|
|
820
|
+
elif key == "artists":
|
|
821
|
+
# Round artist values
|
|
822
|
+
result[key] = [_round_artist(a) for a in value]
|
|
823
|
+
else:
|
|
824
|
+
result[key] = value
|
|
825
|
+
return result
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def _round_style_section(style_data: dict) -> dict:
|
|
829
|
+
"""Round values in hierarchical style section with scopes.
|
|
830
|
+
|
|
831
|
+
Handles structure like:
|
|
832
|
+
{
|
|
833
|
+
"global": {"fonts": {...}, "padding": {...}},
|
|
834
|
+
"axes_default": {"axes": {...}, "ticks": {...}},
|
|
835
|
+
"artist_default": {"lines": {...}, "markers": {...}}
|
|
836
|
+
}
|
|
837
|
+
"""
|
|
838
|
+
result = {}
|
|
839
|
+
for scope, scope_data in style_data.items():
|
|
840
|
+
if scope in ("global", "axes_default", "artist_default"):
|
|
841
|
+
# Handle scope-level dict
|
|
842
|
+
result[scope] = {}
|
|
843
|
+
for category, category_data in scope_data.items():
|
|
844
|
+
if isinstance(category_data, dict):
|
|
845
|
+
result[scope][category] = _round_style_subsection(category, category_data)
|
|
846
|
+
else:
|
|
847
|
+
result[scope][category] = category_data
|
|
848
|
+
elif isinstance(scope_data, dict):
|
|
849
|
+
# Fallback for flat structure (backward compatibility)
|
|
850
|
+
result[scope] = _round_style_subsection(scope, scope_data)
|
|
851
|
+
elif isinstance(scope_data, float):
|
|
852
|
+
if "_mm" in scope:
|
|
853
|
+
result[scope] = _round_value(scope_data, PRECISION["mm"], fixed=True)
|
|
854
|
+
elif "_pt" in scope:
|
|
855
|
+
result[scope] = _round_value(scope_data, 1, fixed=True)
|
|
856
|
+
else:
|
|
857
|
+
result[scope] = _round_value(scope_data, 2)
|
|
858
|
+
elif isinstance(scope_data, int):
|
|
859
|
+
result[scope] = scope_data
|
|
860
|
+
else:
|
|
861
|
+
result[scope] = scope_data
|
|
862
|
+
return result
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def _round_style_subsection(category: str, data: dict) -> dict:
|
|
866
|
+
"""Round values in a style subsection based on category."""
|
|
867
|
+
result = {}
|
|
868
|
+
for key, value in data.items():
|
|
869
|
+
if isinstance(value, float):
|
|
870
|
+
if "_mm" in key or category in ("axes", "ticks", "lines", "markers"):
|
|
871
|
+
# mm values: 2 decimals
|
|
872
|
+
result[key] = _round_value(value, PRECISION["mm"], fixed=True)
|
|
873
|
+
elif "_pt" in key or category in ("fonts", "padding"):
|
|
874
|
+
# pt values: 1 decimal
|
|
875
|
+
result[key] = _round_value(value, 1, fixed=True)
|
|
876
|
+
else:
|
|
877
|
+
result[key] = _round_value(value, 2)
|
|
878
|
+
elif isinstance(value, int):
|
|
879
|
+
result[key] = value
|
|
880
|
+
else:
|
|
881
|
+
result[key] = value
|
|
882
|
+
return result
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def _round_plot_section(plot_data: dict) -> dict:
|
|
886
|
+
"""Round values in plot section."""
|
|
887
|
+
result = {}
|
|
888
|
+
for key, value in plot_data.items():
|
|
889
|
+
if key == "artists":
|
|
890
|
+
result[key] = [_round_artist(a) for a in value]
|
|
891
|
+
elif key == "legend":
|
|
892
|
+
result[key] = value # Legend has no floats to round
|
|
893
|
+
else:
|
|
894
|
+
result[key] = value
|
|
895
|
+
return result
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def _round_artist(artist: dict) -> dict:
|
|
899
|
+
"""Round values in a single artist."""
|
|
900
|
+
result = {}
|
|
901
|
+
for key, value in artist.items():
|
|
902
|
+
if key == "style" and isinstance(value, dict):
|
|
903
|
+
# Legacy: Round values in style dict (for backward compatibility)
|
|
904
|
+
style_result = {}
|
|
905
|
+
for sk, sv in value.items():
|
|
906
|
+
if sk in ("linewidth_pt", "markersize_pt"):
|
|
907
|
+
# Fixed 2 decimals: 0.57
|
|
908
|
+
style_result[sk] = _round_value(sv, PRECISION["linewidth"], fixed=True)
|
|
909
|
+
else:
|
|
910
|
+
style_result[sk] = sv
|
|
911
|
+
result[key] = style_result
|
|
912
|
+
elif key == "backend" and isinstance(value, dict):
|
|
913
|
+
# New two-layer structure: round values in backend.props
|
|
914
|
+
backend_result = {"name": value.get("name", "matplotlib")}
|
|
915
|
+
if "artist_class" in value:
|
|
916
|
+
backend_result["artist_class"] = value["artist_class"]
|
|
917
|
+
if "props" in value and isinstance(value["props"], dict):
|
|
918
|
+
props_result = {}
|
|
919
|
+
for pk, pv in value["props"].items():
|
|
920
|
+
if pk in ("linewidth_pt", "markersize_pt"):
|
|
921
|
+
# Fixed 2 decimals: 0.57
|
|
922
|
+
props_result[pk] = _round_value(pv, PRECISION["linewidth"], fixed=True)
|
|
923
|
+
elif pk == "size":
|
|
924
|
+
# Scatter size: 1 decimal
|
|
925
|
+
props_result[pk] = _round_value(pv, 1, fixed=True)
|
|
926
|
+
else:
|
|
927
|
+
props_result[pk] = pv
|
|
928
|
+
backend_result["props"] = props_result
|
|
929
|
+
result[key] = backend_result
|
|
930
|
+
elif key == "geometry" and isinstance(value, dict):
|
|
931
|
+
# Round geometry values (for bar charts)
|
|
932
|
+
geom_result = {}
|
|
933
|
+
for gk, gv in value.items():
|
|
934
|
+
if isinstance(gv, float):
|
|
935
|
+
geom_result[gk] = _round_value(gv, 4, fixed=False)
|
|
936
|
+
else:
|
|
937
|
+
geom_result[gk] = gv
|
|
938
|
+
result[key] = geom_result
|
|
939
|
+
elif key == "zorder":
|
|
940
|
+
result[key] = int(value) if isinstance(value, (int, float)) else value
|
|
941
|
+
else:
|
|
942
|
+
result[key] = value
|
|
943
|
+
return result
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
# Backward compatibility alias
|
|
947
|
+
_round_trace = _round_artist
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
def _parse_label_unit(label_text: str) -> tuple:
|
|
951
|
+
"""
|
|
952
|
+
Parse label text to extract label and unit.
|
|
953
|
+
|
|
954
|
+
Handles formats like:
|
|
955
|
+
- "Time [s]" -> ("Time", "s")
|
|
956
|
+
- "Amplitude (a.u.)" -> ("Amplitude", "a.u.")
|
|
957
|
+
- "Value" -> ("Value", "")
|
|
958
|
+
|
|
959
|
+
Parameters
|
|
960
|
+
----------
|
|
961
|
+
label_text : str
|
|
962
|
+
The full label text from axes
|
|
963
|
+
|
|
964
|
+
Returns
|
|
965
|
+
-------
|
|
966
|
+
tuple
|
|
967
|
+
(label, unit) where unit is empty string if not found
|
|
968
|
+
"""
|
|
969
|
+
import re
|
|
970
|
+
|
|
971
|
+
if not label_text:
|
|
972
|
+
return "", ""
|
|
973
|
+
|
|
974
|
+
# Try to match [...] pattern first (preferred format)
|
|
975
|
+
match = re.match(r"^(.+?)\s*\[([^\]]+)\]$", label_text)
|
|
976
|
+
if match:
|
|
977
|
+
return match.group(1).strip(), match.group(2).strip()
|
|
978
|
+
|
|
979
|
+
# Try to match (...) pattern
|
|
980
|
+
match = re.match(r"^(.+?)\s*\(([^\)]+)\)$", label_text)
|
|
981
|
+
if match:
|
|
982
|
+
return match.group(1).strip(), match.group(2).strip()
|
|
983
|
+
|
|
984
|
+
# No unit found
|
|
985
|
+
return label_text.strip(), ""
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
def _get_csv_column_names(trace_id: str, ax_row: int = 0, ax_col: int = 0, variables: list = None) -> dict:
|
|
989
|
+
"""
|
|
990
|
+
Get CSV column names using the single source of truth naming convention.
|
|
991
|
+
|
|
992
|
+
Format: ax-row-{row}-col-{col}_trace-id-{id}_variable-{var}
|
|
993
|
+
|
|
994
|
+
Parameters
|
|
995
|
+
----------
|
|
996
|
+
trace_id : str
|
|
997
|
+
The trace identifier (e.g., "sine", "step")
|
|
998
|
+
ax_row : int
|
|
999
|
+
Row position of axes in grid (default: 0)
|
|
1000
|
+
ax_col : int
|
|
1001
|
+
Column position of axes in grid (default: 0)
|
|
1002
|
+
variables : list, optional
|
|
1003
|
+
List of variable names (default: ["x", "y"])
|
|
1004
|
+
|
|
1005
|
+
Returns
|
|
1006
|
+
-------
|
|
1007
|
+
dict
|
|
1008
|
+
Dictionary mapping variable names to CSV column names
|
|
1009
|
+
"""
|
|
1010
|
+
from ._csv_column_naming import get_csv_column_name
|
|
1011
|
+
|
|
1012
|
+
if variables is None:
|
|
1013
|
+
variables = ["x", "y"]
|
|
1014
|
+
|
|
1015
|
+
data_ref = {}
|
|
1016
|
+
for var in variables:
|
|
1017
|
+
data_ref[var] = get_csv_column_name(var, ax_row, ax_col, trace_id=trace_id)
|
|
1018
|
+
|
|
1019
|
+
return data_ref
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
def _extract_artists(ax) -> list:
|
|
1023
|
+
"""
|
|
1024
|
+
Extract artist information including properties and CSV column mapping.
|
|
1025
|
+
|
|
1026
|
+
Uses matplotlib terminology: each drawable element is an Artist.
|
|
1027
|
+
Only includes artists that were explicitly created via scitex tracking (top-level calls),
|
|
1028
|
+
not internal artists created by matplotlib functions like boxplot() which internally
|
|
1029
|
+
call plot() multiple times.
|
|
1030
|
+
|
|
1031
|
+
Parameters
|
|
1032
|
+
----------
|
|
1033
|
+
ax : matplotlib.axes.Axes
|
|
1034
|
+
The axes to extract artists from
|
|
1035
|
+
|
|
1036
|
+
Returns
|
|
1037
|
+
-------
|
|
1038
|
+
list
|
|
1039
|
+
List of artist dictionaries with:
|
|
1040
|
+
- id: unique identifier
|
|
1041
|
+
- artist_class: matplotlib class name (Line2D, PathCollection, etc.)
|
|
1042
|
+
- label: legend label
|
|
1043
|
+
- style: color, linestyle, linewidth, etc.
|
|
1044
|
+
- data_ref: CSV column mapping (matches columns_actual exactly)
|
|
1045
|
+
"""
|
|
1046
|
+
import matplotlib.colors as mcolors
|
|
1047
|
+
|
|
1048
|
+
artists = []
|
|
1049
|
+
|
|
1050
|
+
# Get axes position for CSV column naming
|
|
1051
|
+
ax_row, ax_col = 0, 0 # Default for single axes
|
|
1052
|
+
if hasattr(ax, "_scitex_metadata") and "position_in_grid" in ax._scitex_metadata:
|
|
1053
|
+
pos = ax._scitex_metadata["position_in_grid"]
|
|
1054
|
+
ax_row, ax_col = pos[0], pos[1]
|
|
1055
|
+
|
|
1056
|
+
# Get the raw matplotlib axes for accessing lines
|
|
1057
|
+
mpl_ax = ax._axis_mpl if hasattr(ax, "_axis_mpl") else ax
|
|
1058
|
+
|
|
1059
|
+
# Try to find scitex wrapper for plot type detection and history access
|
|
1060
|
+
ax_for_detection = ax
|
|
1061
|
+
if not hasattr(ax, 'history') and hasattr(mpl_ax, '_scitex_wrapper'):
|
|
1062
|
+
ax_for_detection = mpl_ax._scitex_wrapper
|
|
1063
|
+
|
|
1064
|
+
# Check if we should filter to only tracked artists
|
|
1065
|
+
# For plot types that internally call plot (boxplot, errorbar, etc.),
|
|
1066
|
+
# we don't export the internal artists EXCEPT explicitly tracked ones
|
|
1067
|
+
plot_type, method = _detect_plot_type(ax_for_detection)
|
|
1068
|
+
|
|
1069
|
+
# Plot types where internal line artists should be hidden
|
|
1070
|
+
# But we still export artists that have explicit _scitex_id set
|
|
1071
|
+
# These plot types create Line2D objects internally that don't have
|
|
1072
|
+
# corresponding data in the CSV export
|
|
1073
|
+
# NOTE: scatter is NOT included here because scatter plots often have
|
|
1074
|
+
# regression lines that should be exported
|
|
1075
|
+
internal_plot_types = {
|
|
1076
|
+
"boxplot", "violin", "hist", "bar", "image", "heatmap", "kde", "ecdf",
|
|
1077
|
+
"errorbar", "fill", "stem", "contour", "pie", "quiver", "stream"
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
skip_unlabeled = plot_type in internal_plot_types
|
|
1081
|
+
|
|
1082
|
+
# Build a map from scitex_id to full record from history
|
|
1083
|
+
# Record format: (tracking_id, method, tracked_dict, kwargs)
|
|
1084
|
+
id_to_history = {}
|
|
1085
|
+
if hasattr(ax_for_detection, "history"):
|
|
1086
|
+
for record_id, record in ax_for_detection.history.items():
|
|
1087
|
+
if isinstance(record, tuple) and len(record) >= 2:
|
|
1088
|
+
tracking_id = record[0] # The id used in tracking
|
|
1089
|
+
id_to_history[tracking_id] = record # Store full record
|
|
1090
|
+
|
|
1091
|
+
# Special handling for boxplot and violin - extract semantic components
|
|
1092
|
+
# Boxplot creates lines in a specific pattern: for n boxes, there are
|
|
1093
|
+
# typically: whiskers (2*n), caps (2*n), median (n), fliers (n)
|
|
1094
|
+
is_boxplot = plot_type == "boxplot"
|
|
1095
|
+
is_violin = plot_type == "violin"
|
|
1096
|
+
is_stem = plot_type == "stem"
|
|
1097
|
+
|
|
1098
|
+
# For boxplot, try to determine the number of boxes and compute stats from history
|
|
1099
|
+
num_boxes = 0
|
|
1100
|
+
boxplot_stats = [] # Will hold stats for each box
|
|
1101
|
+
boxplot_data = None
|
|
1102
|
+
if is_boxplot and hasattr(ax_for_detection, "history"):
|
|
1103
|
+
for record in ax_for_detection.history.values():
|
|
1104
|
+
if isinstance(record, tuple) and len(record) >= 3:
|
|
1105
|
+
method_name = record[1]
|
|
1106
|
+
if method_name == "boxplot":
|
|
1107
|
+
tracked_dict = record[2]
|
|
1108
|
+
args = tracked_dict.get("args", [])
|
|
1109
|
+
if args and len(args) > 0:
|
|
1110
|
+
data = args[0]
|
|
1111
|
+
if hasattr(data, '__len__') and not isinstance(data, str):
|
|
1112
|
+
# Check if it's list of arrays or single array
|
|
1113
|
+
if hasattr(data[0], '__len__') and not isinstance(data[0], str):
|
|
1114
|
+
num_boxes = len(data)
|
|
1115
|
+
boxplot_data = data
|
|
1116
|
+
else:
|
|
1117
|
+
num_boxes = 1
|
|
1118
|
+
boxplot_data = [data]
|
|
1119
|
+
break
|
|
1120
|
+
|
|
1121
|
+
# Compute boxplot statistics
|
|
1122
|
+
if boxplot_data is not None:
|
|
1123
|
+
import numpy as np
|
|
1124
|
+
for box_idx, box_data in enumerate(boxplot_data):
|
|
1125
|
+
try:
|
|
1126
|
+
arr = np.asarray(box_data)
|
|
1127
|
+
arr = arr[~np.isnan(arr)] # Remove NaN values
|
|
1128
|
+
if len(arr) > 0:
|
|
1129
|
+
q1 = float(np.percentile(arr, 25))
|
|
1130
|
+
median = float(np.median(arr))
|
|
1131
|
+
q3 = float(np.percentile(arr, 75))
|
|
1132
|
+
iqr = q3 - q1
|
|
1133
|
+
whisker_low = float(max(arr.min(), q1 - 1.5 * iqr))
|
|
1134
|
+
whisker_high = float(min(arr.max(), q3 + 1.5 * iqr))
|
|
1135
|
+
# Fliers are points outside whiskers
|
|
1136
|
+
fliers = arr[(arr < whisker_low) | (arr > whisker_high)]
|
|
1137
|
+
boxplot_stats.append({
|
|
1138
|
+
"box_index": box_idx,
|
|
1139
|
+
"median": median,
|
|
1140
|
+
"q1": q1,
|
|
1141
|
+
"q3": q3,
|
|
1142
|
+
"whisker_low": whisker_low,
|
|
1143
|
+
"whisker_high": whisker_high,
|
|
1144
|
+
"n_fliers": int(len(fliers)),
|
|
1145
|
+
"n_samples": int(len(arr)),
|
|
1146
|
+
})
|
|
1147
|
+
except (ValueError, TypeError):
|
|
1148
|
+
pass
|
|
1149
|
+
|
|
1150
|
+
for i, line in enumerate(mpl_ax.lines):
|
|
1151
|
+
# Get ID from _scitex_id attribute (set by scitex plotting functions)
|
|
1152
|
+
# This matches the id= kwarg passed to ax.plot()
|
|
1153
|
+
scitex_id = getattr(line, "_scitex_id", None)
|
|
1154
|
+
|
|
1155
|
+
# Get label for legend
|
|
1156
|
+
label = line.get_label()
|
|
1157
|
+
|
|
1158
|
+
# For internal plot types (boxplot, violin, etc.), skip Line2D artists
|
|
1159
|
+
# that were created internally by matplotlib (not explicitly tracked).
|
|
1160
|
+
# These internal artists don't have corresponding data in the CSV.
|
|
1161
|
+
# BUT: for boxplot/violin/stem, we want to export with semantic labels
|
|
1162
|
+
semantic_type = None
|
|
1163
|
+
semantic_id = None
|
|
1164
|
+
has_boxplot_stats = False
|
|
1165
|
+
box_idx = None
|
|
1166
|
+
|
|
1167
|
+
# For stem, always detect semantic type (even with scitex_id)
|
|
1168
|
+
if is_stem:
|
|
1169
|
+
marker = line.get_marker()
|
|
1170
|
+
linestyle = line.get_linestyle()
|
|
1171
|
+
if marker and marker != "None" and linestyle == "None":
|
|
1172
|
+
# This is the marker line (markers only, no connecting line)
|
|
1173
|
+
semantic_type = "stem_marker"
|
|
1174
|
+
semantic_id = "stem_markers"
|
|
1175
|
+
elif linestyle and linestyle != "None":
|
|
1176
|
+
# This is either stemlines or baseline
|
|
1177
|
+
# Check if it looks like a baseline (horizontal line at y=0)
|
|
1178
|
+
ydata = line.get_ydata()
|
|
1179
|
+
if len(ydata) >= 2 and len(set(ydata)) == 1:
|
|
1180
|
+
semantic_type = "stem_baseline"
|
|
1181
|
+
semantic_id = "stem_baseline"
|
|
1182
|
+
else:
|
|
1183
|
+
semantic_type = "stem_stem"
|
|
1184
|
+
semantic_id = "stem_lines"
|
|
1185
|
+
else:
|
|
1186
|
+
semantic_type = "stem_component"
|
|
1187
|
+
semantic_id = f"stem_{i}"
|
|
1188
|
+
|
|
1189
|
+
if skip_unlabeled and not scitex_id and label.startswith("_"):
|
|
1190
|
+
# For boxplot, assign semantic roles based on position in lines list
|
|
1191
|
+
if is_boxplot and num_boxes > 0:
|
|
1192
|
+
# Boxplot line order: whiskers (2*n), caps (2*n), medians (n), fliers (n)
|
|
1193
|
+
total_whiskers = 2 * num_boxes
|
|
1194
|
+
total_caps = 2 * num_boxes
|
|
1195
|
+
total_medians = num_boxes
|
|
1196
|
+
|
|
1197
|
+
if i < total_whiskers:
|
|
1198
|
+
box_idx = i // 2
|
|
1199
|
+
whisker_idx = i % 2
|
|
1200
|
+
semantic_type = "boxplot_whisker"
|
|
1201
|
+
semantic_id = f"box_{box_idx}_whisker_{whisker_idx}"
|
|
1202
|
+
elif i < total_whiskers + total_caps:
|
|
1203
|
+
cap_i = i - total_whiskers
|
|
1204
|
+
box_idx = cap_i // 2
|
|
1205
|
+
cap_idx = cap_i % 2
|
|
1206
|
+
semantic_type = "boxplot_cap"
|
|
1207
|
+
semantic_id = f"box_{box_idx}_cap_{cap_idx}"
|
|
1208
|
+
elif i < total_whiskers + total_caps + total_medians:
|
|
1209
|
+
box_idx = i - total_whiskers - total_caps
|
|
1210
|
+
semantic_type = "boxplot_median"
|
|
1211
|
+
semantic_id = f"box_{box_idx}_median"
|
|
1212
|
+
# Mark this as the primary element to hold stats
|
|
1213
|
+
has_boxplot_stats = True
|
|
1214
|
+
else:
|
|
1215
|
+
flier_idx = i - total_whiskers - total_caps - total_medians
|
|
1216
|
+
# Distribute fliers across boxes if we have fewer flier lines than boxes
|
|
1217
|
+
box_idx = flier_idx if flier_idx < num_boxes else num_boxes - 1
|
|
1218
|
+
semantic_type = "boxplot_flier"
|
|
1219
|
+
semantic_id = f"box_{box_idx}_flier"
|
|
1220
|
+
elif is_violin:
|
|
1221
|
+
# Violin typically has: bodies (patches), then optional lines
|
|
1222
|
+
semantic_type = "violin_component"
|
|
1223
|
+
semantic_id = f"violin_line_{i}"
|
|
1224
|
+
elif is_stem:
|
|
1225
|
+
# Already handled above
|
|
1226
|
+
pass
|
|
1227
|
+
else:
|
|
1228
|
+
continue # Skip for other internal plot types
|
|
1229
|
+
|
|
1230
|
+
artist = {}
|
|
1231
|
+
|
|
1232
|
+
# For scatter plots, check if this Line2D is a regression line
|
|
1233
|
+
is_regression_line = False
|
|
1234
|
+
if plot_type == "scatter" and label.startswith("_"):
|
|
1235
|
+
# Check if this looks like a regression line (straight line with few points)
|
|
1236
|
+
xdata = line.get_xdata()
|
|
1237
|
+
ydata = line.get_ydata()
|
|
1238
|
+
if len(xdata) == 2: # Regression line typically has 2 points
|
|
1239
|
+
is_regression_line = True
|
|
1240
|
+
|
|
1241
|
+
# Store display id/label
|
|
1242
|
+
# For stem, use semantic_id as the primary ID to ensure uniqueness
|
|
1243
|
+
if semantic_id and is_stem:
|
|
1244
|
+
artist["id"] = semantic_id
|
|
1245
|
+
if scitex_id:
|
|
1246
|
+
artist["group_id"] = scitex_id # Store original trace id as group
|
|
1247
|
+
elif scitex_id:
|
|
1248
|
+
artist["id"] = scitex_id
|
|
1249
|
+
elif semantic_id:
|
|
1250
|
+
artist["id"] = semantic_id
|
|
1251
|
+
elif is_regression_line:
|
|
1252
|
+
artist["id"] = f"regression_{i}"
|
|
1253
|
+
elif not label.startswith("_"):
|
|
1254
|
+
artist["id"] = label
|
|
1255
|
+
else:
|
|
1256
|
+
artist["id"] = f"line_{i}"
|
|
1257
|
+
|
|
1258
|
+
# Semantic layer: mark (plot type) and role (component role)
|
|
1259
|
+
# mark: line, scatter, bar, boxplot, violin, heatmap, etc.
|
|
1260
|
+
# role: specific component like boxplot_median, violin_body, etc.
|
|
1261
|
+
artist["mark"] = "line" # Line2D is always a line mark
|
|
1262
|
+
if semantic_type:
|
|
1263
|
+
artist["role"] = semantic_type
|
|
1264
|
+
elif is_regression_line:
|
|
1265
|
+
artist["role"] = "regression_line"
|
|
1266
|
+
|
|
1267
|
+
# Label (for legend) - use label if not internal
|
|
1268
|
+
# legend_included indicates if this artist appears in legend
|
|
1269
|
+
if not label.startswith("_"):
|
|
1270
|
+
artist["label"] = label
|
|
1271
|
+
artist["legend_included"] = True
|
|
1272
|
+
else:
|
|
1273
|
+
artist["legend_included"] = False
|
|
1274
|
+
|
|
1275
|
+
# zorder for layering
|
|
1276
|
+
artist["zorder"] = line.get_zorder()
|
|
1277
|
+
|
|
1278
|
+
# Backend layer: matplotlib-specific properties
|
|
1279
|
+
backend = {
|
|
1280
|
+
"name": "matplotlib",
|
|
1281
|
+
"artist_class": type(line).__name__, # e.g., "Line2D"
|
|
1282
|
+
"props": {}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
# Color - always convert to hex for consistent JSON storage
|
|
1286
|
+
color = line.get_color()
|
|
1287
|
+
try:
|
|
1288
|
+
# mcolors.to_hex handles strings, RGB tuples, RGBA tuples
|
|
1289
|
+
color_hex = mcolors.to_hex(color, keep_alpha=False)
|
|
1290
|
+
backend["props"]["color"] = color_hex
|
|
1291
|
+
except (ValueError, TypeError):
|
|
1292
|
+
# Fallback: store as-is
|
|
1293
|
+
backend["props"]["color"] = color
|
|
1294
|
+
|
|
1295
|
+
# Line style
|
|
1296
|
+
backend["props"]["linestyle"] = line.get_linestyle()
|
|
1297
|
+
|
|
1298
|
+
# Line width
|
|
1299
|
+
backend["props"]["linewidth_pt"] = line.get_linewidth()
|
|
1300
|
+
|
|
1301
|
+
# Marker - always include (null if no marker)
|
|
1302
|
+
marker = line.get_marker()
|
|
1303
|
+
if marker and marker != "None" and marker != "none":
|
|
1304
|
+
backend["props"]["marker"] = marker
|
|
1305
|
+
backend["props"]["markersize_pt"] = line.get_markersize()
|
|
1306
|
+
else:
|
|
1307
|
+
backend["props"]["marker"] = None
|
|
1308
|
+
|
|
1309
|
+
artist["backend"] = backend
|
|
1310
|
+
|
|
1311
|
+
# data_ref - CSV column mapping using single source of truth naming
|
|
1312
|
+
# Format: ax-row-{row}-col-{col}_trace-id-{id}_variable-{var}
|
|
1313
|
+
# Only add data_ref if this is NOT a boxplot/violin internal element
|
|
1314
|
+
# (those have semantic_type set but no corresponding CSV data)
|
|
1315
|
+
if not semantic_type:
|
|
1316
|
+
# Try to find the correct trace_id for data_ref
|
|
1317
|
+
# Priority: 1) _scitex_id, 2) History record trace_id, 3) Artist ID
|
|
1318
|
+
trace_id_for_ref = None
|
|
1319
|
+
|
|
1320
|
+
if scitex_id:
|
|
1321
|
+
# Artist has explicit _scitex_id set
|
|
1322
|
+
trace_id_for_ref = scitex_id
|
|
1323
|
+
else:
|
|
1324
|
+
# Try to find matching history record for this Line2D
|
|
1325
|
+
# Look for "plot" method records and match by index
|
|
1326
|
+
if hasattr(ax_for_detection, "history"):
|
|
1327
|
+
plot_records = []
|
|
1328
|
+
for record_id, record in ax_for_detection.history.items():
|
|
1329
|
+
if isinstance(record, tuple) and len(record) >= 2:
|
|
1330
|
+
if record[1] == "plot":
|
|
1331
|
+
# Extract trace_id from tracking_id (e.g., "ax_00_plot_0" -> "0")
|
|
1332
|
+
tracking_id = record[0]
|
|
1333
|
+
if tracking_id.startswith("ax_"):
|
|
1334
|
+
parts = tracking_id.split("_")
|
|
1335
|
+
if len(parts) >= 4:
|
|
1336
|
+
trace_id_for_ref = "_".join(parts[3:])
|
|
1337
|
+
elif len(parts) == 4:
|
|
1338
|
+
trace_id_for_ref = parts[3]
|
|
1339
|
+
elif tracking_id.startswith("plot_"):
|
|
1340
|
+
trace_id_for_ref = tracking_id[5:] if len(tracking_id) > 5 else str(i)
|
|
1341
|
+
else:
|
|
1342
|
+
# User-provided ID like "sine"
|
|
1343
|
+
trace_id_for_ref = tracking_id
|
|
1344
|
+
plot_records.append(trace_id_for_ref)
|
|
1345
|
+
|
|
1346
|
+
# Match by line index if we have plot records
|
|
1347
|
+
if plot_records:
|
|
1348
|
+
# Find the index of this line among all non-semantic lines
|
|
1349
|
+
non_semantic_line_idx = 0
|
|
1350
|
+
for j, l in enumerate(mpl_ax.lines[:i]):
|
|
1351
|
+
l_label = l.get_label()
|
|
1352
|
+
l_scitex_id = getattr(l, "_scitex_id", None)
|
|
1353
|
+
l_semantic_id = getattr(l, "_scitex_semantic_id", None)
|
|
1354
|
+
# Count only lines that would get data_ref (non-semantic)
|
|
1355
|
+
if not l_semantic_id and not l_label.startswith("_"):
|
|
1356
|
+
non_semantic_line_idx += 1
|
|
1357
|
+
elif l_scitex_id:
|
|
1358
|
+
non_semantic_line_idx += 1
|
|
1359
|
+
|
|
1360
|
+
if non_semantic_line_idx < len(plot_records):
|
|
1361
|
+
trace_id_for_ref = plot_records[non_semantic_line_idx]
|
|
1362
|
+
|
|
1363
|
+
# Fallback to artist ID
|
|
1364
|
+
if not trace_id_for_ref:
|
|
1365
|
+
trace_id_for_ref = artist.get("id", str(i))
|
|
1366
|
+
|
|
1367
|
+
artist["data_ref"] = _get_csv_column_names(trace_id_for_ref, ax_row, ax_col)
|
|
1368
|
+
elif is_stem and scitex_id:
|
|
1369
|
+
# For stem artists, add data_ref pointing to the original trace's columns
|
|
1370
|
+
artist["data_ref"] = _get_csv_column_names(scitex_id, ax_row, ax_col)
|
|
1371
|
+
# For baseline, mark it as derived (not directly from CSV)
|
|
1372
|
+
if semantic_type == "stem_baseline":
|
|
1373
|
+
artist["derived"] = True
|
|
1374
|
+
artist["data_ref"]["derived_from"] = "y=0"
|
|
1375
|
+
|
|
1376
|
+
# Add boxplot statistics to the median artist
|
|
1377
|
+
if has_boxplot_stats and box_idx is not None and box_idx < len(boxplot_stats):
|
|
1378
|
+
artist["stats"] = boxplot_stats[box_idx]
|
|
1379
|
+
|
|
1380
|
+
artists.append(artist)
|
|
1381
|
+
|
|
1382
|
+
# Also extract PathCollection artists (scatter points)
|
|
1383
|
+
for i, coll in enumerate(mpl_ax.collections):
|
|
1384
|
+
if "PathCollection" not in type(coll).__name__:
|
|
1385
|
+
continue
|
|
1386
|
+
|
|
1387
|
+
artist = {}
|
|
1388
|
+
|
|
1389
|
+
# Get ID from _scitex_id attribute
|
|
1390
|
+
scitex_id = getattr(coll, "_scitex_id", None)
|
|
1391
|
+
label = coll.get_label()
|
|
1392
|
+
|
|
1393
|
+
if scitex_id:
|
|
1394
|
+
artist["id"] = scitex_id
|
|
1395
|
+
elif label and not label.startswith("_"):
|
|
1396
|
+
artist["id"] = label
|
|
1397
|
+
else:
|
|
1398
|
+
artist["id"] = f"scatter_{i}"
|
|
1399
|
+
|
|
1400
|
+
# Semantic layer
|
|
1401
|
+
artist["mark"] = "scatter"
|
|
1402
|
+
|
|
1403
|
+
# Legend inclusion
|
|
1404
|
+
if label and not label.startswith("_"):
|
|
1405
|
+
artist["label"] = label
|
|
1406
|
+
artist["legend_included"] = True
|
|
1407
|
+
else:
|
|
1408
|
+
artist["legend_included"] = False
|
|
1409
|
+
|
|
1410
|
+
artist["zorder"] = coll.get_zorder()
|
|
1411
|
+
|
|
1412
|
+
# Backend layer: matplotlib-specific properties
|
|
1413
|
+
backend = {
|
|
1414
|
+
"name": "matplotlib",
|
|
1415
|
+
"artist_class": type(coll).__name__, # "PathCollection"
|
|
1416
|
+
"props": {}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
try:
|
|
1420
|
+
facecolors = coll.get_facecolor()
|
|
1421
|
+
if len(facecolors) > 0:
|
|
1422
|
+
backend["props"]["facecolor"] = mcolors.to_hex(facecolors[0], keep_alpha=False)
|
|
1423
|
+
except (ValueError, TypeError, IndexError):
|
|
1424
|
+
pass
|
|
1425
|
+
|
|
1426
|
+
try:
|
|
1427
|
+
edgecolors = coll.get_edgecolor()
|
|
1428
|
+
if len(edgecolors) > 0:
|
|
1429
|
+
backend["props"]["edgecolor"] = mcolors.to_hex(edgecolors[0], keep_alpha=False)
|
|
1430
|
+
except (ValueError, TypeError, IndexError):
|
|
1431
|
+
pass
|
|
1432
|
+
|
|
1433
|
+
try:
|
|
1434
|
+
sizes = coll.get_sizes()
|
|
1435
|
+
if len(sizes) > 0:
|
|
1436
|
+
backend["props"]["size"] = float(sizes[0])
|
|
1437
|
+
except (ValueError, TypeError, IndexError):
|
|
1438
|
+
pass
|
|
1439
|
+
|
|
1440
|
+
artist["backend"] = backend
|
|
1441
|
+
|
|
1442
|
+
# data_ref - CSV column mapping using single source of truth naming
|
|
1443
|
+
# Format: ax-row-{row}-col-{col}_trace-id-{id}_variable-{var}
|
|
1444
|
+
artist_id = artist.get("id", str(i))
|
|
1445
|
+
artist["data_ref"] = _get_csv_column_names(artist_id, ax_row, ax_col)
|
|
1446
|
+
|
|
1447
|
+
artists.append(artist)
|
|
1448
|
+
|
|
1449
|
+
# Extract Rectangle patches (bar/barh/hist charts)
|
|
1450
|
+
# First, collect all rectangles to determine group info
|
|
1451
|
+
rectangles = []
|
|
1452
|
+
for i, patch in enumerate(mpl_ax.patches):
|
|
1453
|
+
patch_type = type(patch).__name__
|
|
1454
|
+
if patch_type == "Rectangle":
|
|
1455
|
+
rectangles.append((i, patch))
|
|
1456
|
+
|
|
1457
|
+
# Determine if this is bar, barh, or hist based on plot_type
|
|
1458
|
+
is_bar = plot_type in ("bar", "barh")
|
|
1459
|
+
is_hist = plot_type == "hist"
|
|
1460
|
+
|
|
1461
|
+
# Get trace_id from history for data_ref
|
|
1462
|
+
trace_id_for_bars = None
|
|
1463
|
+
if hasattr(ax_for_detection, "history"):
|
|
1464
|
+
for record in ax_for_detection.history.values():
|
|
1465
|
+
if isinstance(record, tuple) and len(record) >= 2:
|
|
1466
|
+
method_name = record[1]
|
|
1467
|
+
if method_name in ("bar", "barh", "hist"):
|
|
1468
|
+
trace_id_for_bars = record[0]
|
|
1469
|
+
break
|
|
1470
|
+
|
|
1471
|
+
bar_count = 0
|
|
1472
|
+
for rect_idx, (i, patch) in enumerate(rectangles):
|
|
1473
|
+
patch_type = type(patch).__name__
|
|
1474
|
+
|
|
1475
|
+
# Skip internal unlabeled patches for non-bar/hist types
|
|
1476
|
+
scitex_id = getattr(patch, "_scitex_id", None)
|
|
1477
|
+
label = patch.get_label() if hasattr(patch, "get_label") else ""
|
|
1478
|
+
|
|
1479
|
+
# For bar/hist, we want ALL rectangles even if unlabeled
|
|
1480
|
+
if not (is_bar or is_hist):
|
|
1481
|
+
if skip_unlabeled and not scitex_id and (not label or label.startswith("_")):
|
|
1482
|
+
continue
|
|
1483
|
+
|
|
1484
|
+
artist = {}
|
|
1485
|
+
|
|
1486
|
+
# Generate unique ID with index
|
|
1487
|
+
base_id = scitex_id or (label if label and not label.startswith("_") else trace_id_for_bars or "bar")
|
|
1488
|
+
artist["id"] = f"{base_id}_{bar_count}"
|
|
1489
|
+
|
|
1490
|
+
# Add group_id for referencing the whole group
|
|
1491
|
+
artist["group_id"] = base_id
|
|
1492
|
+
|
|
1493
|
+
# Semantic layer
|
|
1494
|
+
artist["mark"] = "bar"
|
|
1495
|
+
if is_hist:
|
|
1496
|
+
artist["role"] = "hist_bin"
|
|
1497
|
+
else:
|
|
1498
|
+
artist["role"] = "bar_body"
|
|
1499
|
+
|
|
1500
|
+
# Legend inclusion - only first bar of a group should be in legend
|
|
1501
|
+
if label and not label.startswith("_") and bar_count == 0:
|
|
1502
|
+
artist["label"] = label
|
|
1503
|
+
artist["legend_included"] = True
|
|
1504
|
+
else:
|
|
1505
|
+
artist["legend_included"] = False
|
|
1506
|
+
|
|
1507
|
+
artist["zorder"] = patch.get_zorder()
|
|
1508
|
+
|
|
1509
|
+
# Backend layer: matplotlib-specific properties
|
|
1510
|
+
backend = {
|
|
1511
|
+
"name": "matplotlib",
|
|
1512
|
+
"artist_class": patch_type,
|
|
1513
|
+
"props": {}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
try:
|
|
1517
|
+
backend["props"]["facecolor"] = mcolors.to_hex(patch.get_facecolor(), keep_alpha=False)
|
|
1518
|
+
except (ValueError, TypeError):
|
|
1519
|
+
pass
|
|
1520
|
+
try:
|
|
1521
|
+
backend["props"]["edgecolor"] = mcolors.to_hex(patch.get_edgecolor(), keep_alpha=False)
|
|
1522
|
+
except (ValueError, TypeError):
|
|
1523
|
+
pass
|
|
1524
|
+
try:
|
|
1525
|
+
backend["props"]["linewidth_pt"] = patch.get_linewidth()
|
|
1526
|
+
except (ValueError, TypeError):
|
|
1527
|
+
pass
|
|
1528
|
+
|
|
1529
|
+
artist["backend"] = backend
|
|
1530
|
+
|
|
1531
|
+
# Bar geometry
|
|
1532
|
+
try:
|
|
1533
|
+
artist["geometry"] = {
|
|
1534
|
+
"x": patch.get_x(),
|
|
1535
|
+
"y": patch.get_y(),
|
|
1536
|
+
"width": patch.get_width(),
|
|
1537
|
+
"height": patch.get_height(),
|
|
1538
|
+
}
|
|
1539
|
+
except (ValueError, TypeError):
|
|
1540
|
+
pass
|
|
1541
|
+
|
|
1542
|
+
# data_ref with row_index for individual bars
|
|
1543
|
+
if trace_id_for_bars:
|
|
1544
|
+
if is_hist:
|
|
1545
|
+
# Histogram uses specific column names: bin-centers (x), bin-counts (y)
|
|
1546
|
+
prefix = f"ax-row-{ax_row}-col-{ax_col}_trace-id-{trace_id_for_bars}_variable-"
|
|
1547
|
+
artist["data_ref"] = {
|
|
1548
|
+
"x": f"{prefix}bin-centers",
|
|
1549
|
+
"y": f"{prefix}bin-counts",
|
|
1550
|
+
"row_index": bar_count,
|
|
1551
|
+
"bin_index": bar_count,
|
|
1552
|
+
}
|
|
1553
|
+
else:
|
|
1554
|
+
artist["data_ref"] = _get_csv_column_names(trace_id_for_bars, ax_row, ax_col)
|
|
1555
|
+
artist["data_ref"]["row_index"] = bar_count
|
|
1556
|
+
|
|
1557
|
+
bar_count += 1
|
|
1558
|
+
artists.append(artist)
|
|
1559
|
+
|
|
1560
|
+
# Extract Wedge patches (pie charts)
|
|
1561
|
+
wedge_count = 0
|
|
1562
|
+
for i, patch in enumerate(mpl_ax.patches):
|
|
1563
|
+
patch_type = type(patch).__name__
|
|
1564
|
+
|
|
1565
|
+
if patch_type != "Wedge":
|
|
1566
|
+
continue
|
|
1567
|
+
|
|
1568
|
+
artist = {}
|
|
1569
|
+
|
|
1570
|
+
scitex_id = getattr(patch, "_scitex_id", None)
|
|
1571
|
+
label = patch.get_label() if hasattr(patch, "get_label") else ""
|
|
1572
|
+
|
|
1573
|
+
if scitex_id:
|
|
1574
|
+
artist["id"] = scitex_id
|
|
1575
|
+
elif label and not label.startswith("_"):
|
|
1576
|
+
artist["id"] = label
|
|
1577
|
+
else:
|
|
1578
|
+
artist["id"] = f"wedge_{wedge_count}"
|
|
1579
|
+
wedge_count += 1
|
|
1580
|
+
|
|
1581
|
+
# Semantic layer
|
|
1582
|
+
artist["mark"] = "pie"
|
|
1583
|
+
artist["role"] = "pie_wedge"
|
|
1584
|
+
|
|
1585
|
+
if label and not label.startswith("_"):
|
|
1586
|
+
artist["label"] = label
|
|
1587
|
+
artist["legend_included"] = True
|
|
1588
|
+
else:
|
|
1589
|
+
artist["legend_included"] = False
|
|
1590
|
+
|
|
1591
|
+
artist["zorder"] = patch.get_zorder()
|
|
1592
|
+
|
|
1593
|
+
# Backend layer
|
|
1594
|
+
backend = {
|
|
1595
|
+
"name": "matplotlib",
|
|
1596
|
+
"artist_class": patch_type,
|
|
1597
|
+
"props": {}
|
|
1598
|
+
}
|
|
1599
|
+
try:
|
|
1600
|
+
backend["props"]["facecolor"] = mcolors.to_hex(patch.get_facecolor(), keep_alpha=False)
|
|
1601
|
+
except (ValueError, TypeError):
|
|
1602
|
+
pass
|
|
1603
|
+
|
|
1604
|
+
artist["backend"] = backend
|
|
1605
|
+
artists.append(artist)
|
|
1606
|
+
|
|
1607
|
+
# Extract QuadMesh (hist2d) and PolyCollection (hexbin/violin) with colormap info
|
|
1608
|
+
# Try to get hist2d result data from history
|
|
1609
|
+
hist2d_result = None
|
|
1610
|
+
hexbin_result = None
|
|
1611
|
+
if hasattr(ax_for_detection, "history"):
|
|
1612
|
+
for record in ax_for_detection.history.values():
|
|
1613
|
+
if isinstance(record, tuple) and len(record) >= 3:
|
|
1614
|
+
method_name = record[1]
|
|
1615
|
+
tracked_dict = record[2]
|
|
1616
|
+
if method_name == "hist2d" and "result" in tracked_dict:
|
|
1617
|
+
hist2d_result = tracked_dict["result"]
|
|
1618
|
+
elif method_name == "hexbin" and "result" in tracked_dict:
|
|
1619
|
+
hexbin_result = tracked_dict["result"]
|
|
1620
|
+
|
|
1621
|
+
for i, coll in enumerate(mpl_ax.collections):
|
|
1622
|
+
coll_type = type(coll).__name__
|
|
1623
|
+
|
|
1624
|
+
if coll_type == "QuadMesh":
|
|
1625
|
+
artist = {}
|
|
1626
|
+
artist["id"] = f"hist2d_{i}"
|
|
1627
|
+
|
|
1628
|
+
# Semantic layer
|
|
1629
|
+
artist["mark"] = "heatmap"
|
|
1630
|
+
artist["role"] = "hist2d"
|
|
1631
|
+
|
|
1632
|
+
artist["legend_included"] = False
|
|
1633
|
+
artist["zorder"] = coll.get_zorder()
|
|
1634
|
+
|
|
1635
|
+
# Backend layer
|
|
1636
|
+
backend = {
|
|
1637
|
+
"name": "matplotlib",
|
|
1638
|
+
"artist_class": coll_type,
|
|
1639
|
+
"props": {}
|
|
1640
|
+
}
|
|
1641
|
+
try:
|
|
1642
|
+
cmap = coll.get_cmap()
|
|
1643
|
+
if cmap:
|
|
1644
|
+
backend["props"]["cmap"] = cmap.name
|
|
1645
|
+
except (ValueError, TypeError, AttributeError):
|
|
1646
|
+
pass
|
|
1647
|
+
try:
|
|
1648
|
+
backend["props"]["vmin"] = float(coll.norm.vmin) if coll.norm else None
|
|
1649
|
+
backend["props"]["vmax"] = float(coll.norm.vmax) if coll.norm else None
|
|
1650
|
+
except (ValueError, TypeError, AttributeError):
|
|
1651
|
+
pass
|
|
1652
|
+
|
|
1653
|
+
artist["backend"] = backend
|
|
1654
|
+
|
|
1655
|
+
# Extract hist2d result data directly from QuadMesh
|
|
1656
|
+
try:
|
|
1657
|
+
# Get the count array from the QuadMesh
|
|
1658
|
+
arr = coll.get_array()
|
|
1659
|
+
if arr is not None and len(arr) > 0:
|
|
1660
|
+
import numpy as np
|
|
1661
|
+
# QuadMesh from hist2d has counts as flattened array
|
|
1662
|
+
# Try to get coordinates from the mesh
|
|
1663
|
+
coords = coll.get_coordinates()
|
|
1664
|
+
if coords is not None and len(coords) > 0:
|
|
1665
|
+
# coords shape is (n_rows+1, n_cols+1, 2) for 2D hist
|
|
1666
|
+
n_ybins = coords.shape[0] - 1
|
|
1667
|
+
n_xbins = coords.shape[1] - 1
|
|
1668
|
+
|
|
1669
|
+
# Get edges from coordinates
|
|
1670
|
+
xedges = coords[0, :, 0] # First row, all cols, x-coord
|
|
1671
|
+
yedges = coords[:, 0, 1] # All rows, first col, y-coord
|
|
1672
|
+
|
|
1673
|
+
artist["result"] = {
|
|
1674
|
+
"H_shape": [n_ybins, n_xbins],
|
|
1675
|
+
"n_xbins": int(n_xbins),
|
|
1676
|
+
"n_ybins": int(n_ybins),
|
|
1677
|
+
"xedges_range": [float(xedges[0]), float(xedges[-1])],
|
|
1678
|
+
"yedges_range": [float(yedges[0]), float(yedges[-1])],
|
|
1679
|
+
"count_range": [float(arr.min()), float(arr.max())],
|
|
1680
|
+
"total_count": int(arr.sum()),
|
|
1681
|
+
}
|
|
1682
|
+
except (IndexError, TypeError, AttributeError, ValueError):
|
|
1683
|
+
pass
|
|
1684
|
+
|
|
1685
|
+
artists.append(artist)
|
|
1686
|
+
|
|
1687
|
+
elif coll_type == "PolyCollection" or (coll_type == "FillBetweenPolyCollection" and plot_type == "violin"):
|
|
1688
|
+
arr = coll.get_array() if hasattr(coll, "get_array") else None
|
|
1689
|
+
|
|
1690
|
+
# Check if this is hexbin (has array data for counts) or violin body
|
|
1691
|
+
if arr is not None and len(arr) > 0 and plot_type == "hexbin":
|
|
1692
|
+
artist = {}
|
|
1693
|
+
artist["id"] = f"hexbin_{i}"
|
|
1694
|
+
|
|
1695
|
+
# Semantic layer
|
|
1696
|
+
artist["mark"] = "heatmap"
|
|
1697
|
+
artist["role"] = "hexbin"
|
|
1698
|
+
|
|
1699
|
+
artist["legend_included"] = False
|
|
1700
|
+
artist["zorder"] = coll.get_zorder()
|
|
1701
|
+
|
|
1702
|
+
# Backend layer
|
|
1703
|
+
backend = {
|
|
1704
|
+
"name": "matplotlib",
|
|
1705
|
+
"artist_class": coll_type,
|
|
1706
|
+
"props": {}
|
|
1707
|
+
}
|
|
1708
|
+
try:
|
|
1709
|
+
cmap = coll.get_cmap()
|
|
1710
|
+
if cmap:
|
|
1711
|
+
backend["props"]["cmap"] = cmap.name
|
|
1712
|
+
except (ValueError, TypeError, AttributeError):
|
|
1713
|
+
pass
|
|
1714
|
+
try:
|
|
1715
|
+
backend["props"]["vmin"] = float(coll.norm.vmin) if coll.norm else None
|
|
1716
|
+
backend["props"]["vmax"] = float(coll.norm.vmax) if coll.norm else None
|
|
1717
|
+
except (ValueError, TypeError, AttributeError):
|
|
1718
|
+
pass
|
|
1719
|
+
|
|
1720
|
+
artist["backend"] = backend
|
|
1721
|
+
|
|
1722
|
+
# Add hexbin result info directly from the PolyCollection
|
|
1723
|
+
try:
|
|
1724
|
+
artist["result"] = {
|
|
1725
|
+
"n_hexagons": int(len(arr)),
|
|
1726
|
+
"count_range": [float(arr.min()), float(arr.max())] if len(arr) > 0 else None,
|
|
1727
|
+
"total_count": int(arr.sum()),
|
|
1728
|
+
}
|
|
1729
|
+
except (TypeError, AttributeError, ValueError):
|
|
1730
|
+
pass
|
|
1731
|
+
|
|
1732
|
+
artists.append(artist)
|
|
1733
|
+
|
|
1734
|
+
elif plot_type == "violin":
|
|
1735
|
+
# This is a violin body (PolyCollection for violin shape)
|
|
1736
|
+
artist = {}
|
|
1737
|
+
scitex_id = getattr(coll, "_scitex_id", None)
|
|
1738
|
+
label = coll.get_label() if hasattr(coll, "get_label") else ""
|
|
1739
|
+
|
|
1740
|
+
if scitex_id:
|
|
1741
|
+
artist["id"] = f"{scitex_id}_body_{i}"
|
|
1742
|
+
artist["group_id"] = scitex_id
|
|
1743
|
+
else:
|
|
1744
|
+
artist["id"] = f"violin_body_{i}"
|
|
1745
|
+
|
|
1746
|
+
# Semantic layer
|
|
1747
|
+
artist["mark"] = "polygon"
|
|
1748
|
+
artist["role"] = "violin_body"
|
|
1749
|
+
|
|
1750
|
+
artist["legend_included"] = False
|
|
1751
|
+
artist["zorder"] = coll.get_zorder()
|
|
1752
|
+
|
|
1753
|
+
# Backend layer
|
|
1754
|
+
backend = {
|
|
1755
|
+
"name": "matplotlib",
|
|
1756
|
+
"artist_class": coll_type,
|
|
1757
|
+
"props": {}
|
|
1758
|
+
}
|
|
1759
|
+
try:
|
|
1760
|
+
facecolors = coll.get_facecolor()
|
|
1761
|
+
if len(facecolors) > 0:
|
|
1762
|
+
backend["props"]["facecolor"] = mcolors.to_hex(facecolors[0], keep_alpha=False)
|
|
1763
|
+
except (ValueError, TypeError, IndexError):
|
|
1764
|
+
pass
|
|
1765
|
+
try:
|
|
1766
|
+
edgecolors = coll.get_edgecolor()
|
|
1767
|
+
if len(edgecolors) > 0:
|
|
1768
|
+
backend["props"]["edgecolor"] = mcolors.to_hex(edgecolors[0], keep_alpha=False)
|
|
1769
|
+
except (ValueError, TypeError, IndexError):
|
|
1770
|
+
pass
|
|
1771
|
+
|
|
1772
|
+
artist["backend"] = backend
|
|
1773
|
+
artists.append(artist)
|
|
1774
|
+
|
|
1775
|
+
# Extract AxesImage (imshow)
|
|
1776
|
+
for i, img in enumerate(mpl_ax.images):
|
|
1777
|
+
img_type = type(img).__name__
|
|
1778
|
+
|
|
1779
|
+
artist = {}
|
|
1780
|
+
|
|
1781
|
+
scitex_id = getattr(img, "_scitex_id", None)
|
|
1782
|
+
label = img.get_label() if hasattr(img, "get_label") else ""
|
|
1783
|
+
|
|
1784
|
+
if scitex_id:
|
|
1785
|
+
artist["id"] = scitex_id
|
|
1786
|
+
elif label and not label.startswith("_"):
|
|
1787
|
+
artist["id"] = label
|
|
1788
|
+
else:
|
|
1789
|
+
artist["id"] = f"image_{i}"
|
|
1790
|
+
|
|
1791
|
+
# Semantic layer
|
|
1792
|
+
artist["mark"] = "image"
|
|
1793
|
+
artist["role"] = "image"
|
|
1794
|
+
|
|
1795
|
+
artist["legend_included"] = False
|
|
1796
|
+
artist["zorder"] = img.get_zorder()
|
|
1797
|
+
|
|
1798
|
+
# Backend layer
|
|
1799
|
+
backend = {
|
|
1800
|
+
"name": "matplotlib",
|
|
1801
|
+
"artist_class": img_type,
|
|
1802
|
+
"props": {}
|
|
1803
|
+
}
|
|
1804
|
+
try:
|
|
1805
|
+
cmap = img.get_cmap()
|
|
1806
|
+
if cmap:
|
|
1807
|
+
backend["props"]["cmap"] = cmap.name
|
|
1808
|
+
except (ValueError, TypeError, AttributeError):
|
|
1809
|
+
pass
|
|
1810
|
+
try:
|
|
1811
|
+
backend["props"]["vmin"] = float(img.norm.vmin) if img.norm else None
|
|
1812
|
+
backend["props"]["vmax"] = float(img.norm.vmax) if img.norm else None
|
|
1813
|
+
except (ValueError, TypeError, AttributeError):
|
|
1814
|
+
pass
|
|
1815
|
+
try:
|
|
1816
|
+
backend["props"]["interpolation"] = img.get_interpolation()
|
|
1817
|
+
except (ValueError, TypeError, AttributeError):
|
|
1818
|
+
pass
|
|
1819
|
+
|
|
1820
|
+
artist["backend"] = backend
|
|
1821
|
+
artists.append(artist)
|
|
1822
|
+
|
|
1823
|
+
# Extract Text artists (annotations, stats text, etc.)
|
|
1824
|
+
text_count = 0
|
|
1825
|
+
for i, text_obj in enumerate(mpl_ax.texts):
|
|
1826
|
+
text_content = text_obj.get_text()
|
|
1827
|
+
if not text_content or text_content.strip() == "":
|
|
1828
|
+
continue
|
|
1829
|
+
|
|
1830
|
+
artist = {}
|
|
1831
|
+
|
|
1832
|
+
scitex_id = getattr(text_obj, "_scitex_id", None)
|
|
1833
|
+
|
|
1834
|
+
if scitex_id:
|
|
1835
|
+
artist["id"] = scitex_id
|
|
1836
|
+
else:
|
|
1837
|
+
artist["id"] = f"text_{text_count}"
|
|
1838
|
+
|
|
1839
|
+
# Semantic layer
|
|
1840
|
+
artist["mark"] = "text"
|
|
1841
|
+
|
|
1842
|
+
# Try to determine role from content or position
|
|
1843
|
+
pos = text_obj.get_position()
|
|
1844
|
+
# Check if this looks like stats annotation (contains r=, p=, etc.)
|
|
1845
|
+
if any(kw in text_content.lower() for kw in ['r=', 'p=', 'r²=', 'n=']):
|
|
1846
|
+
artist["role"] = "stats_annotation"
|
|
1847
|
+
else:
|
|
1848
|
+
artist["role"] = "annotation"
|
|
1849
|
+
|
|
1850
|
+
artist["legend_included"] = False
|
|
1851
|
+
artist["zorder"] = text_obj.get_zorder()
|
|
1852
|
+
|
|
1853
|
+
# Geometry - text position
|
|
1854
|
+
artist["geometry"] = {
|
|
1855
|
+
"x": pos[0],
|
|
1856
|
+
"y": pos[1],
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
# Text content
|
|
1860
|
+
artist["text"] = text_content
|
|
1861
|
+
|
|
1862
|
+
# Backend layer
|
|
1863
|
+
backend = {
|
|
1864
|
+
"name": "matplotlib",
|
|
1865
|
+
"artist_class": type(text_obj).__name__,
|
|
1866
|
+
"props": {}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
try:
|
|
1870
|
+
color = text_obj.get_color()
|
|
1871
|
+
backend["props"]["color"] = mcolors.to_hex(color, keep_alpha=False)
|
|
1872
|
+
except (ValueError, TypeError):
|
|
1873
|
+
pass
|
|
1874
|
+
|
|
1875
|
+
try:
|
|
1876
|
+
backend["props"]["fontsize_pt"] = text_obj.get_fontsize()
|
|
1877
|
+
except (ValueError, TypeError):
|
|
1878
|
+
pass
|
|
1879
|
+
|
|
1880
|
+
try:
|
|
1881
|
+
backend["props"]["ha"] = text_obj.get_ha()
|
|
1882
|
+
backend["props"]["va"] = text_obj.get_va()
|
|
1883
|
+
except (ValueError, TypeError):
|
|
1884
|
+
pass
|
|
1885
|
+
|
|
1886
|
+
artist["backend"] = backend
|
|
1887
|
+
|
|
1888
|
+
# data_ref for text position - only if text was explicitly tracked (has _scitex_id)
|
|
1889
|
+
# Auto-generated text (like contour clabels, pie labels) doesn't have CSV data
|
|
1890
|
+
if scitex_id:
|
|
1891
|
+
artist["data_ref"] = {
|
|
1892
|
+
"x": f"text_{text_count}_x",
|
|
1893
|
+
"y": f"text_{text_count}_y",
|
|
1894
|
+
"content": f"text_{text_count}_content"
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
text_count += 1
|
|
1898
|
+
artists.append(artist)
|
|
1899
|
+
|
|
1900
|
+
# Extract LineCollection artists (errorbar lines, etc.)
|
|
1901
|
+
for i, coll in enumerate(mpl_ax.collections):
|
|
1902
|
+
coll_type = type(coll).__name__
|
|
1903
|
+
|
|
1904
|
+
if coll_type == "LineCollection":
|
|
1905
|
+
# LineCollection is used for errorbar caps/lines
|
|
1906
|
+
artist = {}
|
|
1907
|
+
|
|
1908
|
+
scitex_id = getattr(coll, "_scitex_id", None)
|
|
1909
|
+
label = coll.get_label() if hasattr(coll, "get_label") else ""
|
|
1910
|
+
|
|
1911
|
+
if scitex_id:
|
|
1912
|
+
artist["id"] = scitex_id
|
|
1913
|
+
elif label and not label.startswith("_"):
|
|
1914
|
+
artist["id"] = label
|
|
1915
|
+
else:
|
|
1916
|
+
artist["id"] = f"linecollection_{i}"
|
|
1917
|
+
|
|
1918
|
+
# Semantic layer - determine role
|
|
1919
|
+
artist["mark"] = "line"
|
|
1920
|
+
# Check if this is an errorbar based on context
|
|
1921
|
+
if plot_type == "bar" or method == "barh":
|
|
1922
|
+
artist["role"] = "errorbar"
|
|
1923
|
+
elif plot_type == "stem":
|
|
1924
|
+
artist["role"] = "stem_stem"
|
|
1925
|
+
artist["id"] = "stem_lines" # Override ID for stem
|
|
1926
|
+
else:
|
|
1927
|
+
artist["role"] = "line_collection"
|
|
1928
|
+
|
|
1929
|
+
artist["legend_included"] = False
|
|
1930
|
+
artist["zorder"] = coll.get_zorder()
|
|
1931
|
+
|
|
1932
|
+
# Backend layer
|
|
1933
|
+
backend = {
|
|
1934
|
+
"name": "matplotlib",
|
|
1935
|
+
"artist_class": coll_type,
|
|
1936
|
+
"props": {}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
try:
|
|
1940
|
+
colors = coll.get_colors()
|
|
1941
|
+
if len(colors) > 0:
|
|
1942
|
+
backend["props"]["color"] = mcolors.to_hex(colors[0], keep_alpha=False)
|
|
1943
|
+
except (ValueError, TypeError, IndexError):
|
|
1944
|
+
pass
|
|
1945
|
+
|
|
1946
|
+
try:
|
|
1947
|
+
linewidths = coll.get_linewidths()
|
|
1948
|
+
if len(linewidths) > 0:
|
|
1949
|
+
backend["props"]["linewidth_pt"] = float(linewidths[0])
|
|
1950
|
+
except (ValueError, TypeError, IndexError):
|
|
1951
|
+
pass
|
|
1952
|
+
|
|
1953
|
+
artist["backend"] = backend
|
|
1954
|
+
|
|
1955
|
+
# Add data_ref for errorbar LineCollections
|
|
1956
|
+
if artist["role"] == "errorbar":
|
|
1957
|
+
# Try to find the trace_id from history
|
|
1958
|
+
errorbar_trace_id = None
|
|
1959
|
+
error_var = "yerr" if method == "bar" else "xerr"
|
|
1960
|
+
if hasattr(ax_for_detection, "history"):
|
|
1961
|
+
for record in ax_for_detection.history.values():
|
|
1962
|
+
if isinstance(record, tuple) and len(record) >= 2:
|
|
1963
|
+
method_name = record[1]
|
|
1964
|
+
if method_name in ("bar", "barh"):
|
|
1965
|
+
errorbar_trace_id = record[0]
|
|
1966
|
+
break
|
|
1967
|
+
if errorbar_trace_id:
|
|
1968
|
+
base_ref = _get_csv_column_names(errorbar_trace_id, ax_row, ax_col)
|
|
1969
|
+
artist["data_ref"] = {
|
|
1970
|
+
"x": base_ref.get("x"),
|
|
1971
|
+
"y": base_ref.get("y"),
|
|
1972
|
+
error_var: f"ax-row-{ax_row}-col-{ax_col}_trace-id-{errorbar_trace_id}_variable-{error_var}"
|
|
1973
|
+
}
|
|
1974
|
+
elif artist["role"] == "stem_stem" and hasattr(ax_for_detection, "history"):
|
|
1975
|
+
# Add data_ref for stem LineCollection
|
|
1976
|
+
for record in ax_for_detection.history.values():
|
|
1977
|
+
if isinstance(record, tuple) and len(record) >= 2:
|
|
1978
|
+
method_name = record[1]
|
|
1979
|
+
if method_name == "stem":
|
|
1980
|
+
stem_trace_id = record[0]
|
|
1981
|
+
artist["data_ref"] = _get_csv_column_names(stem_trace_id, ax_row, ax_col)
|
|
1982
|
+
break
|
|
1983
|
+
|
|
1984
|
+
artists.append(artist)
|
|
1985
|
+
|
|
1986
|
+
return artists
|
|
1987
|
+
|
|
1988
|
+
|
|
1989
|
+
# Backward compatibility alias
|
|
1990
|
+
_extract_traces = _extract_artists
|
|
1991
|
+
|
|
1992
|
+
|
|
1993
|
+
def _extract_legend_info(ax) -> Optional[dict]:
|
|
1994
|
+
"""
|
|
1995
|
+
Extract legend information from axes.
|
|
1996
|
+
|
|
1997
|
+
Uses matplotlib terminology for legend properties.
|
|
1998
|
+
|
|
1999
|
+
Parameters
|
|
2000
|
+
----------
|
|
2001
|
+
ax : matplotlib.axes.Axes
|
|
2002
|
+
The axes to extract legend from
|
|
2003
|
+
|
|
2004
|
+
Returns
|
|
2005
|
+
-------
|
|
2006
|
+
dict or None
|
|
2007
|
+
Legend info dictionary with matplotlib properties, or None if no legend
|
|
2008
|
+
"""
|
|
2009
|
+
legend = ax.get_legend()
|
|
2010
|
+
if legend is None:
|
|
2011
|
+
return None
|
|
2012
|
+
|
|
2013
|
+
legend_info = {
|
|
2014
|
+
"visible": legend.get_visible(),
|
|
2015
|
+
"loc": legend._loc if hasattr(legend, "_loc") else "best",
|
|
2016
|
+
"frameon": legend.get_frame_on() if hasattr(legend, "get_frame_on") else True,
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
# ncol - number of columns
|
|
2020
|
+
if hasattr(legend, "_ncols"):
|
|
2021
|
+
legend_info["ncol"] = legend._ncols
|
|
2022
|
+
elif hasattr(legend, "_ncol"):
|
|
2023
|
+
legend_info["ncol"] = legend._ncol
|
|
2024
|
+
|
|
2025
|
+
# Extract legend handles with artist references
|
|
2026
|
+
# This allows reconstructing the legend by referencing artists
|
|
2027
|
+
handles = []
|
|
2028
|
+
texts = legend.get_texts()
|
|
2029
|
+
legend_handles = legend.legend_handles if hasattr(legend, 'legend_handles') else []
|
|
2030
|
+
|
|
2031
|
+
# Get the raw matplotlib axes for accessing lines to match IDs
|
|
2032
|
+
mpl_ax = ax._axis_mpl if hasattr(ax, "_axis_mpl") else ax
|
|
2033
|
+
|
|
2034
|
+
for i, text in enumerate(texts):
|
|
2035
|
+
label_text = text.get_text()
|
|
2036
|
+
handle_entry = {"label": label_text}
|
|
2037
|
+
|
|
2038
|
+
# Try to get artist_id from corresponding handle
|
|
2039
|
+
artist_id = None
|
|
2040
|
+
if i < len(legend_handles):
|
|
2041
|
+
handle = legend_handles[i]
|
|
2042
|
+
# Check if handle has scitex_id
|
|
2043
|
+
if hasattr(handle, "_scitex_id"):
|
|
2044
|
+
artist_id = handle._scitex_id
|
|
2045
|
+
|
|
2046
|
+
# Fallback: find matching artist by label in axes artists
|
|
2047
|
+
if artist_id is None:
|
|
2048
|
+
# Check lines
|
|
2049
|
+
for line in mpl_ax.lines:
|
|
2050
|
+
line_label = line.get_label()
|
|
2051
|
+
if line_label == label_text:
|
|
2052
|
+
if hasattr(line, "_scitex_id"):
|
|
2053
|
+
artist_id = line._scitex_id
|
|
2054
|
+
elif not line_label.startswith("_"):
|
|
2055
|
+
artist_id = line_label
|
|
2056
|
+
break
|
|
2057
|
+
|
|
2058
|
+
# Check collections (scatter)
|
|
2059
|
+
if artist_id is None:
|
|
2060
|
+
for coll in mpl_ax.collections:
|
|
2061
|
+
coll_label = coll.get_label() if hasattr(coll, "get_label") else ""
|
|
2062
|
+
if coll_label == label_text:
|
|
2063
|
+
if hasattr(coll, "_scitex_id"):
|
|
2064
|
+
artist_id = coll._scitex_id
|
|
2065
|
+
elif coll_label and not coll_label.startswith("_"):
|
|
2066
|
+
artist_id = coll_label
|
|
2067
|
+
break
|
|
2068
|
+
|
|
2069
|
+
# Check patches (bar/hist/pie)
|
|
2070
|
+
if artist_id is None:
|
|
2071
|
+
for patch in mpl_ax.patches:
|
|
2072
|
+
patch_label = patch.get_label() if hasattr(patch, "get_label") else ""
|
|
2073
|
+
if patch_label == label_text:
|
|
2074
|
+
if hasattr(patch, "_scitex_id"):
|
|
2075
|
+
artist_id = patch._scitex_id
|
|
2076
|
+
elif patch_label and not patch_label.startswith("_"):
|
|
2077
|
+
artist_id = patch_label
|
|
2078
|
+
break
|
|
2079
|
+
|
|
2080
|
+
# Check images (imshow)
|
|
2081
|
+
if artist_id is None:
|
|
2082
|
+
for img in mpl_ax.images:
|
|
2083
|
+
img_label = img.get_label() if hasattr(img, "get_label") else ""
|
|
2084
|
+
if img_label == label_text:
|
|
2085
|
+
if hasattr(img, "_scitex_id"):
|
|
2086
|
+
artist_id = img._scitex_id
|
|
2087
|
+
elif img_label and not img_label.startswith("_"):
|
|
2088
|
+
artist_id = img_label
|
|
2089
|
+
break
|
|
2090
|
+
|
|
2091
|
+
if artist_id:
|
|
2092
|
+
handle_entry["artist_id"] = artist_id
|
|
2093
|
+
|
|
2094
|
+
handles.append(handle_entry)
|
|
2095
|
+
|
|
2096
|
+
if handles:
|
|
2097
|
+
legend_info["handles"] = handles
|
|
2098
|
+
|
|
2099
|
+
return legend_info
|
|
2100
|
+
|
|
2101
|
+
|
|
2102
|
+
def _detect_plot_type(ax) -> tuple:
|
|
2103
|
+
"""
|
|
2104
|
+
Detect the primary plot type and method from axes content.
|
|
2105
|
+
|
|
2106
|
+
Checks for:
|
|
2107
|
+
- Lines -> "line"
|
|
2108
|
+
- Scatter collections -> "scatter"
|
|
2109
|
+
- Bar containers -> "bar"
|
|
2110
|
+
- Patches (histogram) -> "hist"
|
|
2111
|
+
- Box plot -> "boxplot"
|
|
2112
|
+
- Violin plot -> "violin"
|
|
2113
|
+
- Image -> "image"
|
|
2114
|
+
- Contour -> "contour"
|
|
2115
|
+
- KDE -> "kde"
|
|
2116
|
+
|
|
2117
|
+
Parameters
|
|
2118
|
+
----------
|
|
2119
|
+
ax : matplotlib.axes.Axes
|
|
2120
|
+
The axes to analyze
|
|
2121
|
+
|
|
2122
|
+
Returns
|
|
2123
|
+
-------
|
|
2124
|
+
tuple
|
|
2125
|
+
(plot_type, method) where method is the actual plotting function used,
|
|
2126
|
+
or (None, None) if unclear
|
|
2127
|
+
"""
|
|
2128
|
+
# Check scitex history FIRST (most reliable for scitex plots)
|
|
2129
|
+
# History format: dict with keys as IDs and values as tuples (id, method, tracked_dict, kwargs)
|
|
2130
|
+
if hasattr(ax, "history") and len(ax.history) > 0:
|
|
2131
|
+
# Get all methods from history
|
|
2132
|
+
methods = []
|
|
2133
|
+
for record in ax.history.values():
|
|
2134
|
+
if isinstance(record, tuple) and len(record) >= 2:
|
|
2135
|
+
methods.append(record[1]) # record[1] is the method name
|
|
2136
|
+
|
|
2137
|
+
# Check methods in priority order (more specific first)
|
|
2138
|
+
for method in methods:
|
|
2139
|
+
if method == "stx_heatmap":
|
|
2140
|
+
return "heatmap", "stx_heatmap"
|
|
2141
|
+
elif method == "stx_kde":
|
|
2142
|
+
return "kde", "stx_kde"
|
|
2143
|
+
elif method == "stx_ecdf":
|
|
2144
|
+
return "ecdf", "stx_ecdf"
|
|
2145
|
+
elif method == "stx_violin":
|
|
2146
|
+
return "violin", "stx_violin"
|
|
2147
|
+
elif method in ("stx_box", "boxplot"):
|
|
2148
|
+
return "boxplot", method
|
|
2149
|
+
elif method == "stx_line":
|
|
2150
|
+
return "line", "stx_line"
|
|
2151
|
+
elif method == "plot_scatter":
|
|
2152
|
+
return "scatter", "plot_scatter"
|
|
2153
|
+
elif method == "stx_mean_std":
|
|
2154
|
+
return "line", "stx_mean_std"
|
|
2155
|
+
elif method == "stx_mean_ci":
|
|
2156
|
+
return "line", "stx_mean_ci"
|
|
2157
|
+
elif method == "stx_median_iqr":
|
|
2158
|
+
return "line", "stx_median_iqr"
|
|
2159
|
+
elif method == "stx_shaded_line":
|
|
2160
|
+
return "line", "stx_shaded_line"
|
|
2161
|
+
elif method == "sns_boxplot":
|
|
2162
|
+
return "boxplot", "sns_boxplot"
|
|
2163
|
+
elif method == "sns_violinplot":
|
|
2164
|
+
return "violin", "sns_violinplot"
|
|
2165
|
+
elif method == "sns_scatterplot":
|
|
2166
|
+
return "scatter", "sns_scatterplot"
|
|
2167
|
+
elif method == "sns_lineplot":
|
|
2168
|
+
return "line", "sns_lineplot"
|
|
2169
|
+
elif method == "sns_histplot":
|
|
2170
|
+
return "hist", "sns_histplot"
|
|
2171
|
+
elif method == "sns_barplot":
|
|
2172
|
+
return "bar", "sns_barplot"
|
|
2173
|
+
elif method == "sns_stripplot":
|
|
2174
|
+
return "scatter", "sns_stripplot"
|
|
2175
|
+
elif method == "sns_kdeplot":
|
|
2176
|
+
return "kde", "sns_kdeplot"
|
|
2177
|
+
elif method == "scatter":
|
|
2178
|
+
return "scatter", "scatter"
|
|
2179
|
+
elif method == "bar":
|
|
2180
|
+
return "bar", "bar"
|
|
2181
|
+
elif method == "barh":
|
|
2182
|
+
return "bar", "barh"
|
|
2183
|
+
elif method == "hist":
|
|
2184
|
+
return "hist", "hist"
|
|
2185
|
+
elif method == "hist2d":
|
|
2186
|
+
return "hist2d", "hist2d"
|
|
2187
|
+
elif method == "hexbin":
|
|
2188
|
+
return "hexbin", "hexbin"
|
|
2189
|
+
elif method == "violinplot":
|
|
2190
|
+
return "violin", "violinplot"
|
|
2191
|
+
elif method == "errorbar":
|
|
2192
|
+
return "errorbar", "errorbar"
|
|
2193
|
+
elif method == "fill_between":
|
|
2194
|
+
return "fill", "fill_between"
|
|
2195
|
+
elif method == "fill_betweenx":
|
|
2196
|
+
return "fill", "fill_betweenx"
|
|
2197
|
+
elif method == "imshow":
|
|
2198
|
+
return "image", "imshow"
|
|
2199
|
+
elif method == "matshow":
|
|
2200
|
+
return "image", "matshow"
|
|
2201
|
+
elif method == "contour":
|
|
2202
|
+
return "contour", "contour"
|
|
2203
|
+
elif method == "contourf":
|
|
2204
|
+
return "contour", "contourf"
|
|
2205
|
+
elif method == "stem":
|
|
2206
|
+
return "stem", "stem"
|
|
2207
|
+
elif method == "step":
|
|
2208
|
+
return "step", "step"
|
|
2209
|
+
elif method == "pie":
|
|
2210
|
+
return "pie", "pie"
|
|
2211
|
+
elif method == "quiver":
|
|
2212
|
+
return "quiver", "quiver"
|
|
2213
|
+
elif method == "streamplot":
|
|
2214
|
+
return "stream", "streamplot"
|
|
2215
|
+
elif method == "plot":
|
|
2216
|
+
return "line", "plot"
|
|
2217
|
+
# Note: "plot" method is handled last as a fallback since boxplot uses it internally
|
|
2218
|
+
|
|
2219
|
+
# Check for images (takes priority)
|
|
2220
|
+
if len(ax.images) > 0:
|
|
2221
|
+
return "image", "imshow"
|
|
2222
|
+
|
|
2223
|
+
# Check for 2D density plots (hist2d, hexbin) - QuadMesh or PolyCollection
|
|
2224
|
+
if hasattr(ax, "collections"):
|
|
2225
|
+
for coll in ax.collections:
|
|
2226
|
+
coll_type = type(coll).__name__
|
|
2227
|
+
if "QuadMesh" in coll_type:
|
|
2228
|
+
return "hist2d", "hist2d"
|
|
2229
|
+
if "PolyCollection" in coll_type and hasattr(coll, "get_array"):
|
|
2230
|
+
# hexbin creates PolyCollection with array data
|
|
2231
|
+
arr = coll.get_array()
|
|
2232
|
+
if arr is not None and len(arr) > 0:
|
|
2233
|
+
return "hexbin", "hexbin"
|
|
2234
|
+
|
|
2235
|
+
# Check for contours
|
|
2236
|
+
if hasattr(ax, "collections"):
|
|
2237
|
+
for coll in ax.collections:
|
|
2238
|
+
if "Contour" in type(coll).__name__:
|
|
2239
|
+
return "contour", "contour"
|
|
2240
|
+
|
|
2241
|
+
# Check for bar plots
|
|
2242
|
+
if len(ax.containers) > 0:
|
|
2243
|
+
# Check if it's a boxplot (has multiple containers with specific structure)
|
|
2244
|
+
if any("boxplot" in str(type(c)).lower() for c in ax.containers):
|
|
2245
|
+
return "boxplot", "boxplot"
|
|
2246
|
+
# Otherwise assume bar plot
|
|
2247
|
+
return "bar", "bar"
|
|
2248
|
+
|
|
2249
|
+
# Check for patches (could be histogram, violin, pie, etc.)
|
|
2250
|
+
if len(ax.patches) > 0:
|
|
2251
|
+
# Check for pie chart (Wedge patches)
|
|
2252
|
+
if any("Wedge" in type(p).__name__ for p in ax.patches):
|
|
2253
|
+
return "pie", "pie"
|
|
2254
|
+
# If there are many rectangular patches, likely histogram
|
|
2255
|
+
if len(ax.patches) > 5:
|
|
2256
|
+
return "hist", "hist"
|
|
2257
|
+
# Check for violin plot
|
|
2258
|
+
if any("Poly" in type(p).__name__ for p in ax.patches):
|
|
2259
|
+
return "violin", "violinplot"
|
|
2260
|
+
|
|
2261
|
+
# Check for scatter plots (PathCollection)
|
|
2262
|
+
if hasattr(ax, "collections") and len(ax.collections) > 0:
|
|
2263
|
+
for coll in ax.collections:
|
|
2264
|
+
if "PathCollection" in type(coll).__name__:
|
|
2265
|
+
return "scatter", "scatter"
|
|
2266
|
+
|
|
2267
|
+
# Check for line plots
|
|
2268
|
+
if len(ax.lines) > 0:
|
|
2269
|
+
# If there are error bars, it might be errorbar plot
|
|
2270
|
+
if any(hasattr(line, "_mpl_error") for line in ax.lines):
|
|
2271
|
+
return "errorbar", "errorbar"
|
|
2272
|
+
return "line", "plot"
|
|
2273
|
+
|
|
2274
|
+
return None, None
|
|
2275
|
+
|
|
2276
|
+
|
|
2277
|
+
def _extract_csv_columns_from_history(ax) -> list:
|
|
2278
|
+
"""
|
|
2279
|
+
Extract CSV column names from scitex history for all plot types.
|
|
2280
|
+
|
|
2281
|
+
This function generates the exact column names that will be produced
|
|
2282
|
+
by export_as_csv(), providing a mapping between JSON metadata and CSV data.
|
|
2283
|
+
|
|
2284
|
+
Parameters
|
|
2285
|
+
----------
|
|
2286
|
+
ax : AxisWrapper or matplotlib.axes.Axes
|
|
2287
|
+
The axes to extract CSV column info from
|
|
2288
|
+
|
|
2289
|
+
Returns
|
|
2290
|
+
-------
|
|
2291
|
+
list
|
|
2292
|
+
List of dictionaries containing CSV column mappings for each tracked plot,
|
|
2293
|
+
e.g., [{"id": "boxplot_0", "method": "boxplot", "columns": ["ax_00_boxplot_0_boxplot_0", "ax_00_boxplot_0_boxplot_1"]}]
|
|
2294
|
+
"""
|
|
2295
|
+
from ._csv_column_naming import get_csv_column_name
|
|
2296
|
+
|
|
2297
|
+
# Get axes position for CSV column naming
|
|
2298
|
+
ax_row, ax_col = 0, 0 # Default for single axes
|
|
2299
|
+
if hasattr(ax, "_scitex_metadata") and "position_in_grid" in ax._scitex_metadata:
|
|
2300
|
+
pos = ax._scitex_metadata["position_in_grid"]
|
|
2301
|
+
ax_row, ax_col = pos[0], pos[1]
|
|
2302
|
+
|
|
2303
|
+
csv_columns_list = []
|
|
2304
|
+
|
|
2305
|
+
# Check if we have scitex history
|
|
2306
|
+
if not hasattr(ax, "history") or len(ax.history) == 0:
|
|
2307
|
+
return csv_columns_list
|
|
2308
|
+
|
|
2309
|
+
# Iterate through history to extract column names
|
|
2310
|
+
# Use enumerate to track trace index for proper CSV column naming
|
|
2311
|
+
for trace_index, (record_id, record) in enumerate(ax.history.items()):
|
|
2312
|
+
if not isinstance(record, tuple) or len(record) < 4:
|
|
2313
|
+
continue
|
|
2314
|
+
|
|
2315
|
+
id_val, method, tracked_dict, kwargs = record
|
|
2316
|
+
|
|
2317
|
+
# Generate column names using the same function as _extract_traces
|
|
2318
|
+
# This ensures consistency between plot.traces.csv_columns and data.columns
|
|
2319
|
+
columns = _get_csv_columns_for_method_with_index(
|
|
2320
|
+
id_val, method, tracked_dict, kwargs, ax_row, ax_col, trace_index
|
|
2321
|
+
)
|
|
2322
|
+
|
|
2323
|
+
if columns:
|
|
2324
|
+
csv_columns_list.append({
|
|
2325
|
+
"id": id_val,
|
|
2326
|
+
"method": method,
|
|
2327
|
+
"columns": columns,
|
|
2328
|
+
})
|
|
2329
|
+
|
|
2330
|
+
return csv_columns_list
|
|
2331
|
+
|
|
2332
|
+
|
|
2333
|
+
def _get_csv_columns_for_method_with_index(
|
|
2334
|
+
id_val, method, tracked_dict, kwargs, ax_row: int, ax_col: int, trace_index: int
|
|
2335
|
+
) -> list:
|
|
2336
|
+
"""
|
|
2337
|
+
Get CSV column names for a specific plotting method using trace index.
|
|
2338
|
+
|
|
2339
|
+
This function uses the same naming convention as _extract_traces to ensure
|
|
2340
|
+
consistency between plot.traces.csv_columns and data.columns.
|
|
2341
|
+
|
|
2342
|
+
Parameters
|
|
2343
|
+
----------
|
|
2344
|
+
id_val : str
|
|
2345
|
+
The plot ID (e.g., "sine", "cosine")
|
|
2346
|
+
method : str
|
|
2347
|
+
The plotting method name (e.g., "plot", "scatter")
|
|
2348
|
+
tracked_dict : dict
|
|
2349
|
+
The tracked data dictionary
|
|
2350
|
+
kwargs : dict
|
|
2351
|
+
The keyword arguments passed to the plot
|
|
2352
|
+
ax_row : int
|
|
2353
|
+
Row index of axes in grid
|
|
2354
|
+
ax_col : int
|
|
2355
|
+
Column index of axes in grid
|
|
2356
|
+
trace_index : int
|
|
2357
|
+
Index of this trace (for deduplication)
|
|
2358
|
+
|
|
2359
|
+
Returns
|
|
2360
|
+
-------
|
|
2361
|
+
list
|
|
2362
|
+
List of column names that will be in the CSV
|
|
2363
|
+
"""
|
|
2364
|
+
from ._csv_column_naming import get_csv_column_name
|
|
2365
|
+
|
|
2366
|
+
columns = []
|
|
2367
|
+
|
|
2368
|
+
# Use simplified variable names (x, y, bins, counts, etc.)
|
|
2369
|
+
# The full context comes from the column name structure:
|
|
2370
|
+
# ax-row_{row}_ax-col_{col}_trace-id_{id}_variable_{var}
|
|
2371
|
+
if method in ("plot", "stx_line"):
|
|
2372
|
+
columns = [
|
|
2373
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2374
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2375
|
+
]
|
|
2376
|
+
elif method in ("scatter", "plot_scatter"):
|
|
2377
|
+
columns = [
|
|
2378
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2379
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2380
|
+
]
|
|
2381
|
+
elif method in ("bar", "barh"):
|
|
2382
|
+
columns = [
|
|
2383
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2384
|
+
get_csv_column_name("height", ax_row, ax_col, trace_index=trace_index),
|
|
2385
|
+
]
|
|
2386
|
+
elif method == "hist":
|
|
2387
|
+
columns = [
|
|
2388
|
+
get_csv_column_name("bins", ax_row, ax_col, trace_index=trace_index),
|
|
2389
|
+
get_csv_column_name("counts", ax_row, ax_col, trace_index=trace_index),
|
|
2390
|
+
]
|
|
2391
|
+
elif method in ("boxplot", "stx_box"):
|
|
2392
|
+
columns = [
|
|
2393
|
+
get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
|
|
2394
|
+
]
|
|
2395
|
+
elif method in ("violinplot", "stx_violin"):
|
|
2396
|
+
columns = [
|
|
2397
|
+
get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
|
|
2398
|
+
]
|
|
2399
|
+
elif method == "errorbar":
|
|
2400
|
+
columns = [
|
|
2401
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2402
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2403
|
+
get_csv_column_name("yerr", ax_row, ax_col, trace_index=trace_index),
|
|
2404
|
+
]
|
|
2405
|
+
elif method == "fill_between":
|
|
2406
|
+
columns = [
|
|
2407
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2408
|
+
get_csv_column_name("y1", ax_row, ax_col, trace_index=trace_index),
|
|
2409
|
+
get_csv_column_name("y2", ax_row, ax_col, trace_index=trace_index),
|
|
2410
|
+
]
|
|
2411
|
+
elif method in ("imshow", "stx_heatmap", "stx_image"):
|
|
2412
|
+
columns = [
|
|
2413
|
+
get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
|
|
2414
|
+
]
|
|
2415
|
+
elif method in ("stx_kde", "stx_ecdf"):
|
|
2416
|
+
columns = [
|
|
2417
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2418
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2419
|
+
]
|
|
2420
|
+
elif method in ("stx_mean_std", "stx_mean_ci", "stx_median_iqr", "stx_shaded_line"):
|
|
2421
|
+
columns = [
|
|
2422
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2423
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2424
|
+
get_csv_column_name("lower", ax_row, ax_col, trace_index=trace_index),
|
|
2425
|
+
get_csv_column_name("upper", ax_row, ax_col, trace_index=trace_index),
|
|
2426
|
+
]
|
|
2427
|
+
elif method.startswith("sns_"):
|
|
2428
|
+
sns_type = method.replace("sns_", "")
|
|
2429
|
+
if sns_type in ("boxplot", "violinplot"):
|
|
2430
|
+
columns = [
|
|
2431
|
+
get_csv_column_name("data", ax_row, ax_col, trace_index=trace_index),
|
|
2432
|
+
]
|
|
2433
|
+
elif sns_type in ("scatterplot", "lineplot"):
|
|
2434
|
+
columns = [
|
|
2435
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2436
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2437
|
+
]
|
|
2438
|
+
elif sns_type == "barplot":
|
|
2439
|
+
columns = [
|
|
2440
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2441
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2442
|
+
]
|
|
2443
|
+
elif sns_type == "histplot":
|
|
2444
|
+
columns = [
|
|
2445
|
+
get_csv_column_name("bins", ax_row, ax_col, trace_index=trace_index),
|
|
2446
|
+
get_csv_column_name("counts", ax_row, ax_col, trace_index=trace_index),
|
|
2447
|
+
]
|
|
2448
|
+
elif sns_type == "kdeplot":
|
|
2449
|
+
columns = [
|
|
2450
|
+
get_csv_column_name("x", ax_row, ax_col, trace_index=trace_index),
|
|
2451
|
+
get_csv_column_name("y", ax_row, ax_col, trace_index=trace_index),
|
|
2452
|
+
]
|
|
2453
|
+
|
|
2454
|
+
return columns
|
|
2455
|
+
|
|
2456
|
+
|
|
2457
|
+
def _compute_csv_hash_from_df(df) -> Optional[str]:
|
|
2458
|
+
"""
|
|
2459
|
+
Compute a hash of CSV data from a DataFrame.
|
|
2460
|
+
|
|
2461
|
+
This is used after actual CSV export to compute the hash from the
|
|
2462
|
+
exact data that was written.
|
|
2463
|
+
|
|
2464
|
+
Parameters
|
|
2465
|
+
----------
|
|
2466
|
+
df : pandas.DataFrame
|
|
2467
|
+
The DataFrame to compute hash from
|
|
2468
|
+
|
|
2469
|
+
Returns
|
|
2470
|
+
-------
|
|
2471
|
+
str or None
|
|
2472
|
+
SHA256 hash of the CSV data (first 16 chars), or None if unable to compute
|
|
2473
|
+
"""
|
|
2474
|
+
import hashlib
|
|
2475
|
+
|
|
2476
|
+
try:
|
|
2477
|
+
if df is None or df.empty:
|
|
2478
|
+
return None
|
|
2479
|
+
|
|
2480
|
+
# Convert to CSV string for hashing
|
|
2481
|
+
csv_string = df.to_csv(index=False)
|
|
2482
|
+
|
|
2483
|
+
# Compute SHA256 hash
|
|
2484
|
+
hash_obj = hashlib.sha256(csv_string.encode("utf-8"))
|
|
2485
|
+
hash_hex = hash_obj.hexdigest()
|
|
2486
|
+
|
|
2487
|
+
# Return first 16 characters for readability
|
|
2488
|
+
return hash_hex[:16]
|
|
2489
|
+
|
|
2490
|
+
except Exception:
|
|
2491
|
+
return None
|
|
2492
|
+
|
|
2493
|
+
|
|
2494
|
+
def _compute_csv_hash(ax_or_df) -> Optional[str]:
|
|
2495
|
+
"""
|
|
2496
|
+
Compute a hash of the CSV data for reproducibility verification.
|
|
2497
|
+
|
|
2498
|
+
The hash is computed from the actual data that would be exported to CSV,
|
|
2499
|
+
allowing verification that JSON and CSV files are in sync.
|
|
2500
|
+
|
|
2501
|
+
Note: The hash is computed from the AxisWrapper's export_as_csv(), which
|
|
2502
|
+
does NOT include the ax_{index:02d}_ prefix. The FigWrapper.export_as_csv()
|
|
2503
|
+
adds this prefix. We replicate this prefix addition here.
|
|
2504
|
+
|
|
2505
|
+
Parameters
|
|
2506
|
+
----------
|
|
2507
|
+
ax_or_df : AxisWrapper, matplotlib.axes.Axes, or pandas.DataFrame
|
|
2508
|
+
The axes to compute CSV hash from, or a pre-exported DataFrame
|
|
2509
|
+
|
|
2510
|
+
Returns
|
|
2511
|
+
-------
|
|
2512
|
+
str or None
|
|
2513
|
+
SHA256 hash of the CSV data (first 16 chars), or None if unable to compute
|
|
2514
|
+
"""
|
|
2515
|
+
import hashlib
|
|
2516
|
+
|
|
2517
|
+
import pandas as pd
|
|
2518
|
+
|
|
2519
|
+
# If it's already a DataFrame, use the direct hash function
|
|
2520
|
+
if isinstance(ax_or_df, pd.DataFrame):
|
|
2521
|
+
return _compute_csv_hash_from_df(ax_or_df)
|
|
2522
|
+
|
|
2523
|
+
ax = ax_or_df
|
|
2524
|
+
|
|
2525
|
+
# Check if we have scitex history with export capability
|
|
2526
|
+
if not hasattr(ax, "export_as_csv"):
|
|
2527
|
+
return None
|
|
2528
|
+
|
|
2529
|
+
try:
|
|
2530
|
+
# For single axes figures (most common case), ax_index = 0
|
|
2531
|
+
ax_index = 0
|
|
2532
|
+
|
|
2533
|
+
# Export the data as CSV from the AxisWrapper
|
|
2534
|
+
df = ax.export_as_csv()
|
|
2535
|
+
|
|
2536
|
+
if df is None or df.empty:
|
|
2537
|
+
return None
|
|
2538
|
+
|
|
2539
|
+
# Add axis prefix to match what FigWrapper.export_as_csv produces
|
|
2540
|
+
# Uses zero-padded index: ax_00_, ax_01_, etc.
|
|
2541
|
+
prefix = f"ax_{ax_index:02d}_"
|
|
2542
|
+
new_cols = []
|
|
2543
|
+
for col in df.columns:
|
|
2544
|
+
col_str = str(col)
|
|
2545
|
+
if not col_str.startswith(prefix):
|
|
2546
|
+
col_str = f"{prefix}{col_str}"
|
|
2547
|
+
new_cols.append(col_str)
|
|
2548
|
+
df.columns = new_cols
|
|
2549
|
+
|
|
2550
|
+
# Convert to CSV string for hashing
|
|
2551
|
+
csv_string = df.to_csv(index=False)
|
|
2552
|
+
|
|
2553
|
+
# Compute SHA256 hash
|
|
2554
|
+
hash_obj = hashlib.sha256(csv_string.encode("utf-8"))
|
|
2555
|
+
hash_hex = hash_obj.hexdigest()
|
|
2556
|
+
|
|
2557
|
+
# Return first 16 characters for readability
|
|
2558
|
+
return hash_hex[:16]
|
|
2559
|
+
|
|
2560
|
+
except Exception:
|
|
2561
|
+
return None
|
|
2562
|
+
|
|
2563
|
+
|
|
2564
|
+
def _get_csv_columns_for_method(id_val, method, tracked_dict, kwargs, ax_index: int) -> list:
|
|
2565
|
+
"""
|
|
2566
|
+
Get CSV column names for a specific plotting method.
|
|
2567
|
+
|
|
2568
|
+
This simulates the actual CSV export to get exact column names.
|
|
2569
|
+
It uses the same formatters that generate the CSV to ensure consistency.
|
|
2570
|
+
|
|
2571
|
+
Architecture note:
|
|
2572
|
+
- CSV formatters (e.g., _format_boxplot) generate columns WITHOUT ax_ prefix
|
|
2573
|
+
- FigWrapper.export_as_csv() adds the ax_{index:02d}_ prefix
|
|
2574
|
+
- This function simulates that process to get the final column names
|
|
2575
|
+
|
|
2576
|
+
Parameters
|
|
2577
|
+
----------
|
|
2578
|
+
id_val : str
|
|
2579
|
+
The plot ID (e.g., "boxplot_0", "plot_0")
|
|
2580
|
+
method : str
|
|
2581
|
+
The plotting method name (e.g., "boxplot", "plot", "scatter")
|
|
2582
|
+
tracked_dict : dict
|
|
2583
|
+
The tracked data dictionary
|
|
2584
|
+
kwargs : dict
|
|
2585
|
+
The keyword arguments passed to the plot
|
|
2586
|
+
ax_index : int
|
|
2587
|
+
Flattened index of axes (0 for single axes, 0-N for multi-axes)
|
|
2588
|
+
|
|
2589
|
+
Returns
|
|
2590
|
+
-------
|
|
2591
|
+
list
|
|
2592
|
+
List of column names that will be in the CSV (exact match)
|
|
2593
|
+
"""
|
|
2594
|
+
# Import the actual formatters to ensure consistency
|
|
2595
|
+
# This is the single source of truth - we use the same code path as CSV export
|
|
2596
|
+
try:
|
|
2597
|
+
from scitex.plt._subplots._export_as_csv import format_record
|
|
2598
|
+
import pandas as pd
|
|
2599
|
+
|
|
2600
|
+
# Construct the record tuple as used in tracking
|
|
2601
|
+
record = (id_val, method, tracked_dict, kwargs)
|
|
2602
|
+
|
|
2603
|
+
# Call the actual formatter to get the DataFrame
|
|
2604
|
+
df = format_record(record)
|
|
2605
|
+
|
|
2606
|
+
if df is not None and not df.empty:
|
|
2607
|
+
# Add the axis prefix (this is what FigWrapper.export_as_csv does)
|
|
2608
|
+
# Uses zero-padded index: ax_00_, ax_01_, etc.
|
|
2609
|
+
prefix = f"ax_{ax_index:02d}_"
|
|
2610
|
+
columns = []
|
|
2611
|
+
for col in df.columns:
|
|
2612
|
+
col_str = str(col)
|
|
2613
|
+
if not col_str.startswith(prefix):
|
|
2614
|
+
col_str = f"{prefix}{col_str}"
|
|
2615
|
+
columns.append(col_str)
|
|
2616
|
+
return columns
|
|
2617
|
+
|
|
2618
|
+
except Exception:
|
|
2619
|
+
# If formatters fail, fall back to pattern-based generation
|
|
2620
|
+
pass
|
|
2621
|
+
|
|
2622
|
+
# Fallback: Pattern-based column name generation
|
|
2623
|
+
# This should rarely be used since we prefer the actual formatter
|
|
2624
|
+
import numpy as np
|
|
2625
|
+
|
|
2626
|
+
prefix = f"ax_{ax_index:02d}_"
|
|
2627
|
+
columns = []
|
|
2628
|
+
|
|
2629
|
+
# Get args from tracked_dict
|
|
2630
|
+
args = tracked_dict.get("args", []) if tracked_dict else []
|
|
2631
|
+
|
|
2632
|
+
if method in ("boxplot", "stx_box"):
|
|
2633
|
+
# Boxplot: one column per box (mirrors _format_boxplot)
|
|
2634
|
+
if len(args) >= 1:
|
|
2635
|
+
data = args[0]
|
|
2636
|
+
labels = kwargs.get("labels", None) if kwargs else None
|
|
2637
|
+
|
|
2638
|
+
from scitex.types import is_listed_X as scitex_types_is_listed_X
|
|
2639
|
+
|
|
2640
|
+
if isinstance(data, np.ndarray) or scitex_types_is_listed_X(data, [float, int]):
|
|
2641
|
+
# Single box
|
|
2642
|
+
if labels and len(labels) == 1:
|
|
2643
|
+
columns.append(f"{prefix}{id_val}_{labels[0]}")
|
|
2644
|
+
else:
|
|
2645
|
+
columns.append(f"{prefix}{id_val}_boxplot_0")
|
|
2646
|
+
else:
|
|
2647
|
+
# Multiple boxes
|
|
2648
|
+
try:
|
|
2649
|
+
num_boxes = len(data)
|
|
2650
|
+
if labels and len(labels) == num_boxes:
|
|
2651
|
+
for label in labels:
|
|
2652
|
+
columns.append(f"{prefix}{id_val}_{label}")
|
|
2653
|
+
else:
|
|
2654
|
+
for i in range(num_boxes):
|
|
2655
|
+
columns.append(f"{prefix}{id_val}_boxplot_{i}")
|
|
2656
|
+
except TypeError:
|
|
2657
|
+
columns.append(f"{prefix}{id_val}_boxplot_0")
|
|
2658
|
+
|
|
2659
|
+
elif method in ("plot", "stx_line"):
|
|
2660
|
+
# Line plot: x and y columns
|
|
2661
|
+
# For single axes (ax_index=0), use simple prefix
|
|
2662
|
+
columns.append(f"{prefix}{id_val}_plot_x")
|
|
2663
|
+
columns.append(f"{prefix}{id_val}_plot_y")
|
|
2664
|
+
|
|
2665
|
+
elif method in ("scatter", "plot_scatter"):
|
|
2666
|
+
columns.append(f"{prefix}{id_val}_scatter_x")
|
|
2667
|
+
columns.append(f"{prefix}{id_val}_scatter_y")
|
|
2668
|
+
|
|
2669
|
+
elif method in ("bar", "barh"):
|
|
2670
|
+
columns.append(f"{prefix}{id_val}_bar_x")
|
|
2671
|
+
columns.append(f"{prefix}{id_val}_bar_height")
|
|
2672
|
+
|
|
2673
|
+
elif method == "hist":
|
|
2674
|
+
columns.append(f"{prefix}{id_val}_hist_bins")
|
|
2675
|
+
columns.append(f"{prefix}{id_val}_hist_counts")
|
|
2676
|
+
|
|
2677
|
+
elif method in ("violinplot", "stx_violin"):
|
|
2678
|
+
if len(args) >= 1:
|
|
2679
|
+
data = args[0]
|
|
2680
|
+
try:
|
|
2681
|
+
num_violins = len(data)
|
|
2682
|
+
for i in range(num_violins):
|
|
2683
|
+
columns.append(f"{prefix}{id_val}_violin_{i}")
|
|
2684
|
+
except TypeError:
|
|
2685
|
+
columns.append(f"{prefix}{id_val}_violin_0")
|
|
2686
|
+
|
|
2687
|
+
elif method == "errorbar":
|
|
2688
|
+
columns.append(f"{prefix}{id_val}_errorbar_x")
|
|
2689
|
+
columns.append(f"{prefix}{id_val}_errorbar_y")
|
|
2690
|
+
columns.append(f"{prefix}{id_val}_errorbar_yerr")
|
|
2691
|
+
|
|
2692
|
+
elif method == "fill_between":
|
|
2693
|
+
columns.append(f"{prefix}{id_val}_fill_x")
|
|
2694
|
+
columns.append(f"{prefix}{id_val}_fill_y1")
|
|
2695
|
+
columns.append(f"{prefix}{id_val}_fill_y2")
|
|
2696
|
+
|
|
2697
|
+
elif method in ("imshow", "stx_heatmap", "stx_image"):
|
|
2698
|
+
if len(args) >= 1:
|
|
2699
|
+
data = args[0]
|
|
2700
|
+
try:
|
|
2701
|
+
if hasattr(data, "shape") and len(data.shape) >= 2:
|
|
2702
|
+
columns.append(f"{prefix}{id_val}_image_data")
|
|
2703
|
+
except (TypeError, AttributeError):
|
|
2704
|
+
pass
|
|
2705
|
+
|
|
2706
|
+
elif method in ("stx_kde", "stx_ecdf"):
|
|
2707
|
+
suffix = method.replace("stx_", "")
|
|
2708
|
+
columns.append(f"{prefix}{id_val}_{suffix}_x")
|
|
2709
|
+
columns.append(f"{prefix}{id_val}_{suffix}_y")
|
|
2710
|
+
|
|
2711
|
+
elif method in ("stx_mean_std", "stx_mean_ci", "stx_median_iqr", "stx_shaded_line"):
|
|
2712
|
+
suffix = method.replace("stx_", "")
|
|
2713
|
+
columns.append(f"{prefix}{id_val}_{suffix}_x")
|
|
2714
|
+
columns.append(f"{prefix}{id_val}_{suffix}_y")
|
|
2715
|
+
columns.append(f"{prefix}{id_val}_{suffix}_lower")
|
|
2716
|
+
columns.append(f"{prefix}{id_val}_{suffix}_upper")
|
|
2717
|
+
|
|
2718
|
+
elif method.startswith("sns_"):
|
|
2719
|
+
sns_type = method.replace("sns_", "")
|
|
2720
|
+
if sns_type in ("boxplot", "violinplot"):
|
|
2721
|
+
columns.append(f"{prefix}{id_val}_{sns_type}_data")
|
|
2722
|
+
elif sns_type in ("scatterplot", "lineplot"):
|
|
2723
|
+
columns.append(f"{prefix}{id_val}_{sns_type}_x")
|
|
2724
|
+
columns.append(f"{prefix}{id_val}_{sns_type}_y")
|
|
2725
|
+
elif sns_type == "barplot":
|
|
2726
|
+
columns.append(f"{prefix}{id_val}_barplot_x")
|
|
2727
|
+
columns.append(f"{prefix}{id_val}_barplot_y")
|
|
2728
|
+
elif sns_type == "histplot":
|
|
2729
|
+
columns.append(f"{prefix}{id_val}_histplot_bins")
|
|
2730
|
+
columns.append(f"{prefix}{id_val}_histplot_counts")
|
|
2731
|
+
elif sns_type == "kdeplot":
|
|
2732
|
+
columns.append(f"{prefix}{id_val}_kdeplot_x")
|
|
2733
|
+
columns.append(f"{prefix}{id_val}_kdeplot_y")
|
|
2734
|
+
|
|
2735
|
+
return columns
|
|
2736
|
+
|
|
2737
|
+
|
|
2738
|
+
def assert_csv_json_consistency(csv_path: str, json_path: str = None) -> None:
|
|
2739
|
+
"""
|
|
2740
|
+
Assert that CSV data file and its JSON metadata are consistent.
|
|
2741
|
+
|
|
2742
|
+
Raises AssertionError if the column names don't match.
|
|
2743
|
+
|
|
2744
|
+
Parameters
|
|
2745
|
+
----------
|
|
2746
|
+
csv_path : str
|
|
2747
|
+
Path to the CSV data file
|
|
2748
|
+
json_path : str, optional
|
|
2749
|
+
Path to the JSON metadata file. If not provided, assumes
|
|
2750
|
+
the JSON is at the same location with .json extension.
|
|
2751
|
+
|
|
2752
|
+
Raises
|
|
2753
|
+
------
|
|
2754
|
+
AssertionError
|
|
2755
|
+
If CSV and JSON column names don't match
|
|
2756
|
+
FileNotFoundError
|
|
2757
|
+
If CSV or JSON files don't exist
|
|
2758
|
+
|
|
2759
|
+
Examples
|
|
2760
|
+
--------
|
|
2761
|
+
>>> assert_csv_json_consistency('/tmp/plot.csv') # Passes silently if valid
|
|
2762
|
+
>>> # Or use in tests:
|
|
2763
|
+
>>> try:
|
|
2764
|
+
... assert_csv_json_consistency('/tmp/plot.csv')
|
|
2765
|
+
... except AssertionError as e:
|
|
2766
|
+
... print(f"Validation failed: {e}")
|
|
2767
|
+
"""
|
|
2768
|
+
result = verify_csv_json_consistency(csv_path, json_path)
|
|
2769
|
+
|
|
2770
|
+
if result['errors']:
|
|
2771
|
+
raise FileNotFoundError('\n'.join(result['errors']))
|
|
2772
|
+
|
|
2773
|
+
if not result['valid']:
|
|
2774
|
+
msg_parts = ["CSV/JSON consistency check failed:"]
|
|
2775
|
+
if result['missing_in_csv']:
|
|
2776
|
+
msg_parts.append(f" columns_actual missing in CSV: {result['missing_in_csv']}")
|
|
2777
|
+
if result['extra_in_csv']:
|
|
2778
|
+
msg_parts.append(f" Extra columns in CSV: {result['extra_in_csv']}")
|
|
2779
|
+
if result.get('data_ref_missing'):
|
|
2780
|
+
msg_parts.append(f" data_ref columns missing in CSV: {result['data_ref_missing']}")
|
|
2781
|
+
raise AssertionError('\n'.join(msg_parts))
|
|
2782
|
+
|
|
2783
|
+
|
|
2784
|
+
def verify_csv_json_consistency(csv_path: str, json_path: str = None) -> dict:
|
|
2785
|
+
"""
|
|
2786
|
+
Verify consistency between CSV data file and its JSON metadata.
|
|
2787
|
+
|
|
2788
|
+
This function checks that:
|
|
2789
|
+
1. Column names in the CSV file match those declared in JSON's columns_actual
|
|
2790
|
+
2. Artist data_ref values in JSON match actual CSV column names
|
|
2791
|
+
|
|
2792
|
+
Parameters
|
|
2793
|
+
----------
|
|
2794
|
+
csv_path : str
|
|
2795
|
+
Path to the CSV data file
|
|
2796
|
+
json_path : str, optional
|
|
2797
|
+
Path to the JSON metadata file. If not provided, assumes
|
|
2798
|
+
the JSON is at the same location with .json extension.
|
|
2799
|
+
|
|
2800
|
+
Returns
|
|
2801
|
+
-------
|
|
2802
|
+
dict
|
|
2803
|
+
Verification result with keys:
|
|
2804
|
+
- 'valid': bool - True if CSV and JSON are consistent
|
|
2805
|
+
- 'csv_columns': list - Column names found in CSV
|
|
2806
|
+
- 'json_columns': list - Column names declared in JSON
|
|
2807
|
+
- 'data_ref_columns': list - Column names from artist data_ref
|
|
2808
|
+
- 'missing_in_csv': list - Columns in JSON but not in CSV
|
|
2809
|
+
- 'extra_in_csv': list - Columns in CSV but not in JSON
|
|
2810
|
+
- 'data_ref_missing': list - data_ref columns not found in CSV
|
|
2811
|
+
- 'errors': list - Any error messages
|
|
2812
|
+
|
|
2813
|
+
Examples
|
|
2814
|
+
--------
|
|
2815
|
+
>>> result = verify_csv_json_consistency('/tmp/plot.csv')
|
|
2816
|
+
>>> print(result['valid'])
|
|
2817
|
+
True
|
|
2818
|
+
>>> print(result['missing_in_csv'])
|
|
2819
|
+
[]
|
|
2820
|
+
"""
|
|
2821
|
+
import json
|
|
2822
|
+
import os
|
|
2823
|
+
import pandas as pd
|
|
2824
|
+
|
|
2825
|
+
result = {
|
|
2826
|
+
'valid': False,
|
|
2827
|
+
'csv_columns': [],
|
|
2828
|
+
'json_columns': [],
|
|
2829
|
+
'data_ref_columns': [],
|
|
2830
|
+
'missing_in_csv': [],
|
|
2831
|
+
'extra_in_csv': [],
|
|
2832
|
+
'data_ref_missing': [],
|
|
2833
|
+
'errors': [],
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
# Determine JSON path
|
|
2837
|
+
if json_path is None:
|
|
2838
|
+
base, _ = os.path.splitext(csv_path)
|
|
2839
|
+
json_path = base + '.json'
|
|
2840
|
+
|
|
2841
|
+
# Check files exist
|
|
2842
|
+
if not os.path.exists(csv_path):
|
|
2843
|
+
result['errors'].append(f"CSV file not found: {csv_path}")
|
|
2844
|
+
return result
|
|
2845
|
+
if not os.path.exists(json_path):
|
|
2846
|
+
result['errors'].append(f"JSON file not found: {json_path}")
|
|
2847
|
+
return result
|
|
2848
|
+
|
|
2849
|
+
try:
|
|
2850
|
+
# Read CSV columns
|
|
2851
|
+
df = pd.read_csv(csv_path, nrows=0) # Just read header
|
|
2852
|
+
csv_columns = list(df.columns)
|
|
2853
|
+
result['csv_columns'] = csv_columns
|
|
2854
|
+
except Exception as e:
|
|
2855
|
+
result['errors'].append(f"Error reading CSV: {e}")
|
|
2856
|
+
return result
|
|
2857
|
+
|
|
2858
|
+
try:
|
|
2859
|
+
# Read JSON metadata
|
|
2860
|
+
with open(json_path, 'r') as f:
|
|
2861
|
+
metadata = json.load(f)
|
|
2862
|
+
|
|
2863
|
+
# Get columns_actual from data section
|
|
2864
|
+
json_columns = []
|
|
2865
|
+
if 'data' in metadata and 'columns_actual' in metadata['data']:
|
|
2866
|
+
json_columns = metadata['data']['columns_actual']
|
|
2867
|
+
result['json_columns'] = json_columns
|
|
2868
|
+
|
|
2869
|
+
# Extract data_ref columns from artists
|
|
2870
|
+
# Skip 'derived_from' key as it contains descriptive text, not CSV column names
|
|
2871
|
+
# Also skip 'row_index' as it's a numeric index, not a column name
|
|
2872
|
+
data_ref_columns = []
|
|
2873
|
+
skip_keys = {'derived_from', 'row_index'}
|
|
2874
|
+
if 'axes' in metadata:
|
|
2875
|
+
for ax_key, ax_data in metadata['axes'].items():
|
|
2876
|
+
if 'artists' in ax_data:
|
|
2877
|
+
for artist in ax_data['artists']:
|
|
2878
|
+
if 'data_ref' in artist:
|
|
2879
|
+
for key, val in artist['data_ref'].items():
|
|
2880
|
+
if key not in skip_keys and isinstance(val, str):
|
|
2881
|
+
data_ref_columns.append(val)
|
|
2882
|
+
result['data_ref_columns'] = data_ref_columns
|
|
2883
|
+
|
|
2884
|
+
except Exception as e:
|
|
2885
|
+
result['errors'].append(f"Error reading JSON: {e}")
|
|
2886
|
+
return result
|
|
2887
|
+
|
|
2888
|
+
# Compare columns_actual with CSV
|
|
2889
|
+
csv_set = set(csv_columns)
|
|
2890
|
+
json_set = set(json_columns)
|
|
2891
|
+
|
|
2892
|
+
result['missing_in_csv'] = list(json_set - csv_set)
|
|
2893
|
+
result['extra_in_csv'] = list(csv_set - json_set)
|
|
2894
|
+
|
|
2895
|
+
# Check data_ref columns exist in CSV (if there are any)
|
|
2896
|
+
if data_ref_columns:
|
|
2897
|
+
data_ref_set = set(data_ref_columns)
|
|
2898
|
+
result['data_ref_missing'] = list(data_ref_set - csv_set)
|
|
2899
|
+
|
|
2900
|
+
# Valid only if columns_actual matches AND data_ref columns are found in CSV
|
|
2901
|
+
result['valid'] = (
|
|
2902
|
+
len(result['missing_in_csv']) == 0 and
|
|
2903
|
+
len(result['extra_in_csv']) == 0 and
|
|
2904
|
+
len(result['data_ref_missing']) == 0
|
|
2905
|
+
)
|
|
2906
|
+
|
|
2907
|
+
return result
|
|
2908
|
+
|
|
2909
|
+
|
|
2910
|
+
def collect_recipe_metadata(
|
|
2911
|
+
fig,
|
|
2912
|
+
ax=None,
|
|
2913
|
+
auto_crop: bool = True,
|
|
2914
|
+
crop_margin_mm: float = 1.0,
|
|
2915
|
+
) -> Dict:
|
|
2916
|
+
"""
|
|
2917
|
+
Collect minimal "recipe" metadata from figure - method calls + data refs.
|
|
2918
|
+
|
|
2919
|
+
Unlike `collect_figure_metadata()` which captures every rendered artist,
|
|
2920
|
+
this function captures only what's needed to reproduce the figure:
|
|
2921
|
+
- Figure/axes dimensions and limits
|
|
2922
|
+
- Method calls with arguments (from ax.history)
|
|
2923
|
+
- Data column references for CSV linkage
|
|
2924
|
+
- Cropping settings
|
|
2925
|
+
|
|
2926
|
+
This produces much smaller JSON files (e.g., 60 lines vs 1300 for histogram).
|
|
2927
|
+
|
|
2928
|
+
Parameters
|
|
2929
|
+
----------
|
|
2930
|
+
fig : matplotlib.figure.Figure
|
|
2931
|
+
Figure to collect metadata from
|
|
2932
|
+
ax : matplotlib.axes.Axes or AxisWrapper, optional
|
|
2933
|
+
Primary axes to collect from. If not provided, uses first axes.
|
|
2934
|
+
auto_crop : bool, optional
|
|
2935
|
+
Whether auto-cropping is enabled. Default is True.
|
|
2936
|
+
crop_margin_mm : float, optional
|
|
2937
|
+
Margin in mm for auto-cropping. Default is 1.0.
|
|
2938
|
+
|
|
2939
|
+
Returns
|
|
2940
|
+
-------
|
|
2941
|
+
dict
|
|
2942
|
+
Minimal metadata dictionary with structure:
|
|
2943
|
+
- scitex_schema: "scitex.plt.figure.recipe"
|
|
2944
|
+
- scitex_schema_version: "0.2.0"
|
|
2945
|
+
- figure: {size_mm, dpi, mode, auto_crop, crop_margin_mm}
|
|
2946
|
+
- axes: {ax_00: {xaxis, yaxis, calls: [...]}}
|
|
2947
|
+
- data: {csv_path, columns}
|
|
2948
|
+
|
|
2949
|
+
Examples
|
|
2950
|
+
--------
|
|
2951
|
+
>>> fig, ax = scitex.plt.subplots()
|
|
2952
|
+
>>> ax.hist(data, bins=40, id="histogram")
|
|
2953
|
+
>>> metadata = collect_recipe_metadata(fig, ax)
|
|
2954
|
+
>>> # Result has ~60 lines instead of ~1300
|
|
2955
|
+
"""
|
|
2956
|
+
import datetime
|
|
2957
|
+
import uuid
|
|
2958
|
+
|
|
2959
|
+
import matplotlib
|
|
2960
|
+
import scitex
|
|
2961
|
+
|
|
2962
|
+
metadata = {
|
|
2963
|
+
"scitex_schema": "scitex.plt.figure.recipe",
|
|
2964
|
+
"scitex_schema_version": "0.2.0",
|
|
2965
|
+
"figure_uuid": str(uuid.uuid4()),
|
|
2966
|
+
"runtime": {
|
|
2967
|
+
"scitex_version": scitex.__version__,
|
|
2968
|
+
"matplotlib_version": matplotlib.__version__,
|
|
2969
|
+
"created_at": datetime.datetime.now().isoformat(),
|
|
2970
|
+
},
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
# Collect axes - handle AxesWrapper (multi-axes) properly
|
|
2974
|
+
all_axes = [] # List of (ax_wrapper, row, col) tuples
|
|
2975
|
+
grid_shape = (1, 1)
|
|
2976
|
+
|
|
2977
|
+
if ax is not None:
|
|
2978
|
+
# Handle AxesWrapper (multi-axes) - extract individual AxisWrappers with positions
|
|
2979
|
+
if hasattr(ax, "_axes_scitex"):
|
|
2980
|
+
import numpy as np
|
|
2981
|
+
axes_array = ax._axes_scitex
|
|
2982
|
+
if isinstance(axes_array, np.ndarray):
|
|
2983
|
+
grid_shape = axes_array.shape
|
|
2984
|
+
for idx, ax_item in enumerate(axes_array.flat):
|
|
2985
|
+
row = idx // grid_shape[1]
|
|
2986
|
+
col = idx % grid_shape[1]
|
|
2987
|
+
all_axes.append((ax_item, row, col))
|
|
2988
|
+
else:
|
|
2989
|
+
all_axes = [(axes_array, 0, 0)]
|
|
2990
|
+
# Handle AxisWrapper (single axes)
|
|
2991
|
+
elif hasattr(ax, "_axis_mpl"):
|
|
2992
|
+
all_axes = [(ax, 0, 0)]
|
|
2993
|
+
else:
|
|
2994
|
+
# Assume it's a matplotlib axes
|
|
2995
|
+
all_axes = [(ax, 0, 0)]
|
|
2996
|
+
elif hasattr(fig, "axes") and len(fig.axes) > 0:
|
|
2997
|
+
# Fallback to figure axes (linear indexing)
|
|
2998
|
+
for idx, ax_item in enumerate(fig.axes):
|
|
2999
|
+
all_axes.append((ax_item, 0, idx))
|
|
3000
|
+
|
|
3001
|
+
# Figure-level properties
|
|
3002
|
+
if all_axes:
|
|
3003
|
+
try:
|
|
3004
|
+
from ._figure_from_axes_mm import get_dimension_info
|
|
3005
|
+
first_ax_tuple = all_axes[0]
|
|
3006
|
+
first_ax = first_ax_tuple[0]
|
|
3007
|
+
# Get underlying matplotlib axis if wrapped
|
|
3008
|
+
mpl_ax = getattr(first_ax, '_axis_mpl', first_ax)
|
|
3009
|
+
dim_info = get_dimension_info(fig, mpl_ax)
|
|
3010
|
+
|
|
3011
|
+
# Convert to plain lists/floats for JSON serialization
|
|
3012
|
+
size_mm = dim_info["figure_size_mm"]
|
|
3013
|
+
if hasattr(size_mm, 'tolist'):
|
|
3014
|
+
size_mm = size_mm.tolist()
|
|
3015
|
+
elif isinstance(size_mm, (list, tuple)):
|
|
3016
|
+
size_mm = [float(v) if hasattr(v, 'value') else v for v in size_mm]
|
|
3017
|
+
|
|
3018
|
+
metadata["figure"] = {
|
|
3019
|
+
"size_mm": size_mm,
|
|
3020
|
+
"dpi": int(dim_info["dpi"]),
|
|
3021
|
+
"auto_crop": auto_crop,
|
|
3022
|
+
"crop_margin_mm": crop_margin_mm,
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
# Add top-level axes_bbox_px for canvas/web alignment (x0/y0/x1/y1 format)
|
|
3026
|
+
# x0: left edge (Y-axis position), y1: bottom edge (X-axis position)
|
|
3027
|
+
if "axes_bbox_px" in dim_info:
|
|
3028
|
+
bbox = dim_info["axes_bbox_px"]
|
|
3029
|
+
metadata["axes_bbox_px"] = {
|
|
3030
|
+
"x0": int(bbox["x0"]),
|
|
3031
|
+
"y0": int(bbox["y0"]),
|
|
3032
|
+
"x1": int(bbox["x1"]),
|
|
3033
|
+
"y1": int(bbox["y1"]),
|
|
3034
|
+
"width": int(bbox["width"]),
|
|
3035
|
+
"height": int(bbox["height"]),
|
|
3036
|
+
}
|
|
3037
|
+
if "axes_bbox_mm" in dim_info:
|
|
3038
|
+
bbox = dim_info["axes_bbox_mm"]
|
|
3039
|
+
metadata["axes_bbox_mm"] = {
|
|
3040
|
+
"x0": round(float(bbox["x0"]), 2),
|
|
3041
|
+
"y0": round(float(bbox["y0"]), 2),
|
|
3042
|
+
"x1": round(float(bbox["x1"]), 2),
|
|
3043
|
+
"y1": round(float(bbox["y1"]), 2),
|
|
3044
|
+
"width": round(float(bbox["width"]), 2),
|
|
3045
|
+
"height": round(float(bbox["height"]), 2),
|
|
3046
|
+
}
|
|
3047
|
+
except Exception:
|
|
3048
|
+
pass
|
|
3049
|
+
|
|
3050
|
+
# Add mode from scitex metadata
|
|
3051
|
+
scitex_meta = None
|
|
3052
|
+
if ax is not None and hasattr(ax, "_scitex_metadata"):
|
|
3053
|
+
scitex_meta = ax._scitex_metadata
|
|
3054
|
+
elif hasattr(fig, "_scitex_metadata"):
|
|
3055
|
+
scitex_meta = fig._scitex_metadata
|
|
3056
|
+
|
|
3057
|
+
if scitex_meta:
|
|
3058
|
+
if "figure" not in metadata:
|
|
3059
|
+
metadata["figure"] = {}
|
|
3060
|
+
if "mode" in scitex_meta:
|
|
3061
|
+
metadata["figure"]["mode"] = scitex_meta["mode"]
|
|
3062
|
+
# Include style_mm for reproducibility (thickness, fonts, etc.)
|
|
3063
|
+
if "style_mm" in scitex_meta:
|
|
3064
|
+
metadata["style"] = scitex_meta["style_mm"]
|
|
3065
|
+
|
|
3066
|
+
# Collect per-axes metadata with calls
|
|
3067
|
+
if all_axes:
|
|
3068
|
+
metadata["axes"] = {}
|
|
3069
|
+
for current_ax, row, col in all_axes:
|
|
3070
|
+
# Use row-col format: ax_00, ax_01, ax_10, ax_11 for 2x2 grid
|
|
3071
|
+
ax_key = f"ax_{row}{col}"
|
|
3072
|
+
|
|
3073
|
+
# Get underlying matplotlib axis if wrapped
|
|
3074
|
+
mpl_ax = getattr(current_ax, '_axis_mpl', current_ax)
|
|
3075
|
+
|
|
3076
|
+
ax_meta = {
|
|
3077
|
+
"grid_position": {"row": row, "col": col}
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
# Additional position info from scitex_metadata if available
|
|
3081
|
+
if hasattr(current_ax, "_scitex_metadata"):
|
|
3082
|
+
pos = current_ax._scitex_metadata.get("position_in_grid")
|
|
3083
|
+
if pos:
|
|
3084
|
+
ax_meta["grid_position"] = {"row": pos[0], "col": pos[1]}
|
|
3085
|
+
|
|
3086
|
+
# Axis labels and limits (minimal - for axis alignment)
|
|
3087
|
+
try:
|
|
3088
|
+
xlim = mpl_ax.get_xlim()
|
|
3089
|
+
ylim = mpl_ax.get_ylim()
|
|
3090
|
+
ax_meta["xaxis"] = {
|
|
3091
|
+
"label": mpl_ax.get_xlabel() or "",
|
|
3092
|
+
"lim": [round(xlim[0], 4), round(xlim[1], 4)],
|
|
3093
|
+
}
|
|
3094
|
+
ax_meta["yaxis"] = {
|
|
3095
|
+
"label": mpl_ax.get_ylabel() or "",
|
|
3096
|
+
"lim": [round(ylim[0], 4), round(ylim[1], 4)],
|
|
3097
|
+
}
|
|
3098
|
+
except Exception:
|
|
3099
|
+
pass
|
|
3100
|
+
|
|
3101
|
+
# Method calls from history - the core "recipe"
|
|
3102
|
+
# Pass row and col for proper data_ref column naming
|
|
3103
|
+
ax_index = row * grid_shape[1] + col
|
|
3104
|
+
ax_meta["calls"] = _extract_calls_from_history(current_ax, ax_index)
|
|
3105
|
+
|
|
3106
|
+
metadata["axes"][ax_key] = ax_meta
|
|
3107
|
+
|
|
3108
|
+
return metadata
|
|
3109
|
+
|
|
3110
|
+
|
|
3111
|
+
def _extract_calls_from_history(ax, ax_index: int) -> List[dict]:
|
|
3112
|
+
"""
|
|
3113
|
+
Extract method call records from axis history.
|
|
3114
|
+
|
|
3115
|
+
Parameters
|
|
3116
|
+
----------
|
|
3117
|
+
ax : AxisWrapper or matplotlib.axes.Axes
|
|
3118
|
+
Axis to extract history from
|
|
3119
|
+
ax_index : int
|
|
3120
|
+
Index of axis in figure (for CSV column naming)
|
|
3121
|
+
|
|
3122
|
+
Returns
|
|
3123
|
+
-------
|
|
3124
|
+
list
|
|
3125
|
+
List of call records: [{id, method, data_ref, kwargs}, ...]
|
|
3126
|
+
"""
|
|
3127
|
+
calls = []
|
|
3128
|
+
|
|
3129
|
+
# Check for scitex wrapper with history
|
|
3130
|
+
if not hasattr(ax, 'history') and not hasattr(ax, '_ax_history'):
|
|
3131
|
+
return calls
|
|
3132
|
+
|
|
3133
|
+
# Get history dict
|
|
3134
|
+
history = getattr(ax, 'history', None)
|
|
3135
|
+
if history is None:
|
|
3136
|
+
history = getattr(ax, '_ax_history', {})
|
|
3137
|
+
|
|
3138
|
+
# Get grid position
|
|
3139
|
+
ax_row = 0
|
|
3140
|
+
ax_col = 0
|
|
3141
|
+
if hasattr(ax, "_scitex_metadata"):
|
|
3142
|
+
pos = ax._scitex_metadata.get("position_in_grid", [0, 0])
|
|
3143
|
+
ax_row, ax_col = pos[0], pos[1]
|
|
3144
|
+
|
|
3145
|
+
for trace_id, record in history.items():
|
|
3146
|
+
# record format: (id, method_name, tracked_dict, kwargs)
|
|
3147
|
+
if not isinstance(record, (list, tuple)) or len(record) < 3:
|
|
3148
|
+
continue
|
|
3149
|
+
|
|
3150
|
+
call_id, method_name, tracked_dict = record[0], record[1], record[2]
|
|
3151
|
+
kwargs = record[3] if len(record) > 3 else {}
|
|
3152
|
+
|
|
3153
|
+
call = {
|
|
3154
|
+
"id": str(call_id),
|
|
3155
|
+
"method": method_name,
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
# Build data_ref from tracked_dict to CSV column names
|
|
3159
|
+
data_ref = _build_data_ref(call_id, method_name, tracked_dict, ax_row, ax_col)
|
|
3160
|
+
if data_ref:
|
|
3161
|
+
call["data_ref"] = data_ref
|
|
3162
|
+
|
|
3163
|
+
# Filter kwargs to only style-relevant ones (not data)
|
|
3164
|
+
style_kwargs = _filter_style_kwargs(kwargs, method_name)
|
|
3165
|
+
if style_kwargs:
|
|
3166
|
+
call["kwargs"] = style_kwargs
|
|
3167
|
+
|
|
3168
|
+
calls.append(call)
|
|
3169
|
+
|
|
3170
|
+
return calls
|
|
3171
|
+
|
|
3172
|
+
|
|
3173
|
+
def _build_data_ref(trace_id, method_name: str, tracked_dict: dict,
|
|
3174
|
+
ax_row: int, ax_col: int) -> dict:
|
|
3175
|
+
"""
|
|
3176
|
+
Build data_ref mapping from tracked_dict to CSV column names.
|
|
3177
|
+
|
|
3178
|
+
Parameters
|
|
3179
|
+
----------
|
|
3180
|
+
trace_id : str
|
|
3181
|
+
Trace identifier
|
|
3182
|
+
method_name : str
|
|
3183
|
+
Name of the method called
|
|
3184
|
+
tracked_dict : dict
|
|
3185
|
+
Data tracked by the method (contains arrays, dataframes)
|
|
3186
|
+
ax_row, ax_col : int
|
|
3187
|
+
Axis position in grid
|
|
3188
|
+
|
|
3189
|
+
Returns
|
|
3190
|
+
-------
|
|
3191
|
+
dict
|
|
3192
|
+
Mapping of variable names to CSV column names
|
|
3193
|
+
"""
|
|
3194
|
+
prefix = f"ax-row-{ax_row}-col-{ax_col}_trace-id-{trace_id}_variable-"
|
|
3195
|
+
|
|
3196
|
+
data_ref = {}
|
|
3197
|
+
|
|
3198
|
+
# Method-specific column naming
|
|
3199
|
+
if method_name == 'hist':
|
|
3200
|
+
# Histogram: raw data + computed bins
|
|
3201
|
+
data_ref["raw_data"] = f"{prefix}raw-data"
|
|
3202
|
+
data_ref["bin_centers"] = f"{prefix}bin-centers"
|
|
3203
|
+
data_ref["bin_counts"] = f"{prefix}bin-counts"
|
|
3204
|
+
elif method_name in ('plot', 'scatter', 'step', 'errorbar'):
|
|
3205
|
+
# Standard x, y plots
|
|
3206
|
+
data_ref["x"] = f"{prefix}x"
|
|
3207
|
+
data_ref["y"] = f"{prefix}y"
|
|
3208
|
+
# Check for error bars in tracked_dict
|
|
3209
|
+
if tracked_dict and 'yerr' in tracked_dict:
|
|
3210
|
+
data_ref["yerr"] = f"{prefix}yerr"
|
|
3211
|
+
if tracked_dict and 'xerr' in tracked_dict:
|
|
3212
|
+
data_ref["xerr"] = f"{prefix}xerr"
|
|
3213
|
+
elif method_name in ('bar', 'barh'):
|
|
3214
|
+
data_ref["x"] = f"{prefix}x"
|
|
3215
|
+
data_ref["y"] = f"{prefix}y"
|
|
3216
|
+
elif method_name == 'stem':
|
|
3217
|
+
data_ref["x"] = f"{prefix}x"
|
|
3218
|
+
data_ref["y"] = f"{prefix}y"
|
|
3219
|
+
elif method_name in ('fill_between', 'fill_betweenx'):
|
|
3220
|
+
data_ref["x"] = f"{prefix}x"
|
|
3221
|
+
data_ref["y1"] = f"{prefix}y1"
|
|
3222
|
+
data_ref["y2"] = f"{prefix}y2"
|
|
3223
|
+
elif method_name in ('imshow', 'matshow', 'pcolormesh'):
|
|
3224
|
+
data_ref["data"] = f"{prefix}data"
|
|
3225
|
+
elif method_name in ('contour', 'contourf'):
|
|
3226
|
+
data_ref["x"] = f"{prefix}x"
|
|
3227
|
+
data_ref["y"] = f"{prefix}y"
|
|
3228
|
+
data_ref["z"] = f"{prefix}z"
|
|
3229
|
+
elif method_name in ('boxplot', 'violinplot'):
|
|
3230
|
+
data_ref["data"] = f"{prefix}data"
|
|
3231
|
+
elif method_name == 'pie':
|
|
3232
|
+
data_ref["x"] = f"{prefix}x"
|
|
3233
|
+
elif method_name in ('quiver', 'streamplot'):
|
|
3234
|
+
data_ref["x"] = f"{prefix}x"
|
|
3235
|
+
data_ref["y"] = f"{prefix}y"
|
|
3236
|
+
data_ref["u"] = f"{prefix}u"
|
|
3237
|
+
data_ref["v"] = f"{prefix}v"
|
|
3238
|
+
elif method_name == 'hexbin':
|
|
3239
|
+
data_ref["x"] = f"{prefix}x"
|
|
3240
|
+
data_ref["y"] = f"{prefix}y"
|
|
3241
|
+
elif method_name == 'hist2d':
|
|
3242
|
+
data_ref["x"] = f"{prefix}x"
|
|
3243
|
+
data_ref["y"] = f"{prefix}y"
|
|
3244
|
+
elif method_name == 'kde':
|
|
3245
|
+
data_ref["x"] = f"{prefix}x"
|
|
3246
|
+
data_ref["y"] = f"{prefix}y"
|
|
3247
|
+
# SciTeX custom methods (stx_*) - use same naming as matplotlib wrappers
|
|
3248
|
+
elif method_name == 'stx_line':
|
|
3249
|
+
data_ref["x"] = f"{prefix}x"
|
|
3250
|
+
data_ref["y"] = f"{prefix}y"
|
|
3251
|
+
elif method_name in ('stx_mean_std', 'stx_mean_ci', 'stx_median_iqr', 'stx_shaded_line'):
|
|
3252
|
+
data_ref["x"] = f"{prefix}x"
|
|
3253
|
+
data_ref["y_lower"] = f"{prefix}y-lower"
|
|
3254
|
+
data_ref["y_middle"] = f"{prefix}y-middle"
|
|
3255
|
+
data_ref["y_upper"] = f"{prefix}y-upper"
|
|
3256
|
+
elif method_name in ('stx_box', 'stx_violin'):
|
|
3257
|
+
data_ref["data"] = f"{prefix}data"
|
|
3258
|
+
elif method_name == 'stx_scatter_hist':
|
|
3259
|
+
data_ref["x"] = f"{prefix}x"
|
|
3260
|
+
data_ref["y"] = f"{prefix}y"
|
|
3261
|
+
elif method_name in ('stx_heatmap', 'stx_conf_mat', 'stx_image', 'stx_raster'):
|
|
3262
|
+
data_ref["data"] = f"{prefix}data"
|
|
3263
|
+
elif method_name in ('stx_kde', 'stx_ecdf'):
|
|
3264
|
+
data_ref["x"] = f"{prefix}x"
|
|
3265
|
+
data_ref["y"] = f"{prefix}y"
|
|
3266
|
+
elif method_name.startswith('stx_'):
|
|
3267
|
+
# Generic fallback for other stx_ methods
|
|
3268
|
+
data_ref["x"] = f"{prefix}x"
|
|
3269
|
+
data_ref["y"] = f"{prefix}y"
|
|
3270
|
+
else:
|
|
3271
|
+
# Generic fallback for tracked data
|
|
3272
|
+
if tracked_dict:
|
|
3273
|
+
if 'x' in tracked_dict or 'args' in tracked_dict:
|
|
3274
|
+
data_ref["x"] = f"{prefix}x"
|
|
3275
|
+
data_ref["y"] = f"{prefix}y"
|
|
3276
|
+
|
|
3277
|
+
return data_ref
|
|
3278
|
+
|
|
3279
|
+
|
|
3280
|
+
def _filter_style_kwargs(kwargs: dict, method_name: str) -> dict:
|
|
3281
|
+
"""
|
|
3282
|
+
Filter kwargs to only include style-relevant parameters.
|
|
3283
|
+
|
|
3284
|
+
Removes data arrays and internal parameters, keeps style settings
|
|
3285
|
+
that affect appearance (color, linewidth, etc.).
|
|
3286
|
+
|
|
3287
|
+
Parameters
|
|
3288
|
+
----------
|
|
3289
|
+
kwargs : dict
|
|
3290
|
+
Original keyword arguments
|
|
3291
|
+
method_name : str
|
|
3292
|
+
Name of the method
|
|
3293
|
+
|
|
3294
|
+
Returns
|
|
3295
|
+
-------
|
|
3296
|
+
dict
|
|
3297
|
+
Filtered kwargs with only style parameters
|
|
3298
|
+
"""
|
|
3299
|
+
if not kwargs:
|
|
3300
|
+
return {}
|
|
3301
|
+
|
|
3302
|
+
# Style-relevant kwargs to keep
|
|
3303
|
+
style_keys = {
|
|
3304
|
+
'color', 'c', 'facecolor', 'edgecolor', 'linecolor',
|
|
3305
|
+
'linewidth', 'lw', 'linestyle', 'ls',
|
|
3306
|
+
'marker', 'markersize', 'ms', 'markerfacecolor', 'markeredgecolor',
|
|
3307
|
+
'alpha', 'zorder',
|
|
3308
|
+
'label',
|
|
3309
|
+
'bins', 'density', 'histtype', 'orientation',
|
|
3310
|
+
'width', 'height', 'align',
|
|
3311
|
+
'cmap', 'vmin', 'vmax', 'norm',
|
|
3312
|
+
'levels', 'extend',
|
|
3313
|
+
'scale', 'units',
|
|
3314
|
+
'autopct', 'explode', 'shadow', 'startangle',
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
filtered = {}
|
|
3318
|
+
for key, value in kwargs.items():
|
|
3319
|
+
if key in style_keys:
|
|
3320
|
+
# Skip if value is a large array (data, not style)
|
|
3321
|
+
if hasattr(value, '__len__') and not isinstance(value, str):
|
|
3322
|
+
if len(value) > 10:
|
|
3323
|
+
continue
|
|
3324
|
+
# Round float values to 4 decimal places for cleaner JSON
|
|
3325
|
+
if isinstance(value, float):
|
|
3326
|
+
value = round(value, 4)
|
|
3327
|
+
filtered[key] = value
|
|
3328
|
+
|
|
3329
|
+
return filtered
|
|
3330
|
+
|
|
3331
|
+
|
|
3332
|
+
if __name__ == "__main__":
|
|
3333
|
+
import numpy as np
|
|
3334
|
+
|
|
3335
|
+
from ._figure_from_axes_mm import create_axes_with_size_mm
|
|
3336
|
+
|
|
3337
|
+
print("=" * 60)
|
|
3338
|
+
print("METADATA COLLECTION DEMO")
|
|
3339
|
+
print("=" * 60)
|
|
3340
|
+
|
|
3341
|
+
# Create a figure with mm control
|
|
3342
|
+
print("\n1. Creating figure with mm control...")
|
|
3343
|
+
fig, ax = create_axes_with_size_mm(
|
|
3344
|
+
axes_width_mm=30,
|
|
3345
|
+
axes_height_mm=21,
|
|
3346
|
+
mode="publication",
|
|
3347
|
+
style_mm={
|
|
3348
|
+
"axis_thickness_mm": 0.2,
|
|
3349
|
+
"trace_thickness_mm": 0.12,
|
|
3350
|
+
"tick_length_mm": 0.8,
|
|
3351
|
+
},
|
|
3352
|
+
)
|
|
3353
|
+
|
|
3354
|
+
# Plot something
|
|
3355
|
+
x = np.linspace(0, 2 * np.pi, 100)
|
|
3356
|
+
ax.plot(x, np.sin(x), "b-")
|
|
3357
|
+
ax.set_xlabel("X axis")
|
|
3358
|
+
ax.set_ylabel("Y axis")
|
|
3359
|
+
|
|
3360
|
+
# Collect metadata
|
|
3361
|
+
print("\n2. Collecting metadata...")
|
|
3362
|
+
metadata = collect_figure_metadata(fig, ax)
|
|
3363
|
+
|
|
3364
|
+
# Display metadata
|
|
3365
|
+
print("\n3. Collected metadata:")
|
|
3366
|
+
print("-" * 60)
|
|
3367
|
+
import json
|
|
3368
|
+
|
|
3369
|
+
print(json.dumps(metadata, indent=2))
|
|
3370
|
+
print("-" * 60)
|
|
3371
|
+
|
|
3372
|
+
print("\n✅ Metadata collection complete!")
|
|
3373
|
+
print("\nKey fields collected:")
|
|
3374
|
+
print(f" • Software version: {metadata['scitex']['version']}")
|
|
3375
|
+
print(f" • Timestamp: {metadata['scitex']['created_at']}")
|
|
3376
|
+
if "dimensions" in metadata:
|
|
3377
|
+
print(f" • Axes size: {metadata['dimensions']['axes_size_mm']} mm")
|
|
3378
|
+
print(f" • DPI: {metadata['dimensions']['dpi']}")
|
|
3379
|
+
if "runtime" in metadata and "mode" in metadata["runtime"]:
|
|
3380
|
+
print(f" • Mode: {metadata['scitex']['mode']}")
|
|
3381
|
+
if "runtime" in metadata and "style_mm" in metadata["runtime"]:
|
|
3382
|
+
print(" • Style: Embedded ✓")
|
|
3383
|
+
|
|
3384
|
+
# EOF
|