scitex 2.5.0__py3-none-any.whl → 2.7.3__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/__init__.py +25 -10
- scitex/__main__.py +2 -1
- scitex/__version__.py +1 -1
- scitex/_optional_deps.py +13 -20
- scitex/ai/__init__.py +5 -0
- scitex/ai/_gen_ai/_Anthropic.py +3 -1
- scitex/ai/_gen_ai/_BaseGenAI.py +3 -2
- scitex/ai/_gen_ai/_DeepSeek.py +1 -1
- scitex/ai/_gen_ai/_Google.py +3 -2
- scitex/ai/_gen_ai/_Llama.py +4 -2
- scitex/ai/_gen_ai/_OpenAI.py +3 -1
- scitex/ai/_gen_ai/_PARAMS.py +1 -0
- scitex/ai/_gen_ai/_Perplexity.py +3 -1
- scitex/ai/_gen_ai/__init__.py +1 -0
- scitex/ai/_gen_ai/_format_output_func.py +3 -1
- scitex/ai/classification/CrossValidationExperiment.py +8 -14
- scitex/ai/classification/examples/timeseries_cv_demo.py +128 -112
- scitex/ai/classification/reporters/_BaseClassificationReporter.py +2 -0
- scitex/ai/classification/reporters/_ClassificationReporter.py +30 -45
- scitex/ai/classification/reporters/_MultiClassificationReporter.py +8 -11
- scitex/ai/classification/reporters/_SingleClassificationReporter.py +126 -182
- scitex/ai/classification/reporters/__init__.py +1 -1
- scitex/ai/classification/reporters/reporter_utils/_Plotter.py +213 -119
- scitex/ai/classification/reporters/reporter_utils/__init__.py +28 -36
- scitex/ai/classification/reporters/reporter_utils/aggregation.py +125 -143
- scitex/ai/classification/reporters/reporter_utils/data_models.py +128 -120
- scitex/ai/classification/reporters/reporter_utils/reporting.py +507 -340
- scitex/ai/classification/reporters/reporter_utils/storage.py +4 -1
- scitex/ai/classification/reporters/reporter_utils/validation.py +141 -154
- scitex/ai/classification/timeseries/_TimeSeriesBlockingSplit.py +204 -129
- scitex/ai/classification/timeseries/_TimeSeriesCalendarSplit.py +215 -171
- scitex/ai/classification/timeseries/_TimeSeriesMetadata.py +17 -17
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +67 -143
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit_v01-not-using-n_splits.py +67 -143
- scitex/ai/classification/timeseries/_TimeSeriesStrategy.py +12 -13
- scitex/ai/classification/timeseries/_TimeSeriesStratifiedSplit.py +231 -144
- scitex/ai/classification/timeseries/__init__.py +2 -4
- scitex/ai/classification/timeseries/_normalize_timestamp.py +3 -0
- scitex/ai/clustering/_pca.py +0 -1
- scitex/ai/clustering/_umap.py +1 -2
- scitex/ai/feature_extraction/__init__.py +10 -8
- scitex/ai/feature_extraction/vit.py +0 -1
- scitex/ai/feature_selection/feature_selection.py +3 -8
- scitex/ai/metrics/_calc_conf_mat.py +2 -0
- scitex/ai/metrics/_calc_feature_importance.py +3 -7
- scitex/ai/metrics/_calc_pre_rec_auc.py +5 -5
- scitex/ai/metrics/_calc_roc_auc.py +4 -2
- scitex/ai/metrics/_calc_seizure_prediction_metrics.py +35 -20
- scitex/ai/metrics/_calc_silhouette_score.py +1 -3
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger.py +0 -3
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger2020.py +0 -3
- scitex/ai/optim/Ranger_Deep_Learning_Optimizer/ranger/ranger913A.py +0 -3
- scitex/ai/optim/_optimizers.py +1 -1
- scitex/ai/plt/__init__.py +6 -1
- scitex/ai/plt/_plot_feature_importance.py +1 -3
- scitex/ai/plt/_plot_learning_curve.py +9 -24
- scitex/ai/plt/_plot_optuna_study.py +4 -3
- scitex/ai/plt/_plot_pre_rec_curve.py +9 -15
- scitex/ai/plt/_plot_roc_curve.py +6 -8
- scitex/ai/plt/_stx_conf_mat.py +121 -122
- scitex/ai/sampling/undersample.py +3 -2
- scitex/ai/sklearn/__init__.py +2 -2
- scitex/ai/training/_LearningCurveLogger.py +23 -10
- scitex/ai/utils/_check_params.py +0 -1
- scitex/audio/README.md +52 -0
- scitex/audio/__init__.py +384 -0
- scitex/audio/__main__.py +129 -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 +143 -0
- scitex/audio/engines/gtts_engine.py +162 -0
- scitex/audio/engines/pyttsx3_engine.py +131 -0
- scitex/audio/mcp_server.py +757 -0
- scitex/benchmark/__init__.py +15 -25
- scitex/benchmark/benchmark.py +124 -117
- scitex/benchmark/monitor.py +117 -107
- scitex/benchmark/profiler.py +61 -58
- scitex/bridge/__init__.py +110 -0
- scitex/bridge/_helpers.py +149 -0
- scitex/bridge/_plt_vis.py +529 -0
- scitex/bridge/_protocol.py +283 -0
- scitex/bridge/_stats_plt.py +261 -0
- scitex/bridge/_stats_vis.py +265 -0
- scitex/browser/__init__.py +0 -2
- scitex/browser/auth/__init__.py +0 -0
- scitex/browser/auth/google.py +16 -11
- scitex/browser/automation/CookieHandler.py +2 -3
- scitex/browser/collaboration/__init__.py +3 -0
- scitex/browser/collaboration/auth_helpers.py +3 -1
- scitex/browser/collaboration/collaborative_agent.py +2 -0
- scitex/browser/collaboration/interactive_panel.py +2 -2
- scitex/browser/collaboration/shared_session.py +20 -11
- scitex/browser/collaboration/standard_interactions.py +1 -0
- scitex/browser/core/BrowserMixin.py +12 -30
- scitex/browser/core/ChromeProfileManager.py +9 -24
- scitex/browser/debugging/_browser_logger.py +15 -25
- scitex/browser/debugging/_failure_capture.py +9 -2
- scitex/browser/debugging/_highlight_element.py +15 -6
- scitex/browser/debugging/_show_grid.py +5 -6
- scitex/browser/debugging/_sync_session.py +4 -3
- scitex/browser/debugging/_test_monitor.py +14 -5
- scitex/browser/debugging/_visual_cursor.py +46 -35
- scitex/browser/interaction/click_center.py +4 -3
- scitex/browser/interaction/click_with_fallbacks.py +7 -10
- scitex/browser/interaction/close_popups.py +79 -66
- scitex/browser/interaction/fill_with_fallbacks.py +8 -8
- scitex/browser/pdf/__init__.py +3 -1
- scitex/browser/pdf/click_download_for_chrome_pdf_viewer.py +11 -10
- scitex/browser/pdf/detect_chrome_pdf_viewer.py +3 -6
- scitex/browser/remote/CaptchaHandler.py +109 -96
- scitex/browser/remote/ZenRowsAPIClient.py +91 -97
- scitex/browser/remote/ZenRowsBrowserManager.py +138 -112
- scitex/browser/stealth/HumanBehavior.py +4 -9
- scitex/browser/stealth/StealthManager.py +11 -26
- scitex/capture/__init__.py +17 -17
- scitex/capture/__main__.py +2 -3
- scitex/capture/capture.py +23 -51
- scitex/capture/cli.py +14 -39
- scitex/capture/gif.py +5 -9
- scitex/capture/mcp_server.py +7 -20
- scitex/capture/session.py +4 -3
- scitex/capture/utils.py +18 -53
- scitex/cli/__init__.py +1 -1
- scitex/cli/cloud.py +158 -116
- scitex/cli/config.py +224 -0
- scitex/cli/main.py +41 -40
- scitex/cli/scholar.py +60 -27
- scitex/cli/security.py +14 -20
- scitex/cli/web.py +87 -90
- scitex/cli/writer.py +51 -45
- scitex/cloud/__init__.py +14 -11
- scitex/cloud/_matplotlib_hook.py +6 -6
- scitex/config/README.md +313 -0
- scitex/config/{PriorityConfig.py → _PriorityConfig.py} +114 -17
- scitex/config/_ScitexConfig.py +319 -0
- scitex/config/__init__.py +41 -9
- scitex/config/_paths.py +325 -0
- scitex/config/default.yaml +81 -0
- scitex/context/_suppress_output.py +2 -3
- scitex/db/_BaseMixins/_BaseBackupMixin.py +3 -1
- scitex/db/_BaseMixins/_BaseBatchMixin.py +3 -1
- scitex/db/_BaseMixins/_BaseBlobMixin.py +3 -1
- scitex/db/_BaseMixins/_BaseImportExportMixin.py +1 -3
- scitex/db/_BaseMixins/_BaseIndexMixin.py +3 -1
- scitex/db/_BaseMixins/_BaseMaintenanceMixin.py +1 -3
- scitex/db/_BaseMixins/_BaseQueryMixin.py +3 -1
- scitex/db/_BaseMixins/_BaseRowMixin.py +3 -1
- scitex/db/_BaseMixins/_BaseTableMixin.py +3 -1
- scitex/db/_BaseMixins/_BaseTransactionMixin.py +1 -3
- scitex/db/_BaseMixins/__init__.py +1 -1
- scitex/db/__init__.py +9 -1
- scitex/db/__main__.py +8 -21
- scitex/db/_check_health.py +15 -31
- scitex/db/_delete_duplicates.py +7 -4
- scitex/db/_inspect.py +22 -38
- scitex/db/_inspect_optimized.py +89 -85
- scitex/db/_postgresql/_PostgreSQL.py +0 -1
- scitex/db/_postgresql/_PostgreSQLMixins/_BlobMixin.py +3 -1
- scitex/db/_postgresql/_PostgreSQLMixins/_ConnectionMixin.py +1 -3
- scitex/db/_postgresql/_PostgreSQLMixins/_ImportExportMixin.py +1 -3
- scitex/db/_postgresql/_PostgreSQLMixins/_MaintenanceMixin.py +1 -4
- scitex/db/_postgresql/_PostgreSQLMixins/_QueryMixin.py +3 -3
- scitex/db/_postgresql/_PostgreSQLMixins/_RowMixin.py +3 -1
- scitex/db/_postgresql/_PostgreSQLMixins/_TransactionMixin.py +1 -3
- scitex/db/_postgresql/__init__.py +1 -1
- scitex/db/_sqlite3/_SQLite3.py +2 -4
- scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin.py +11 -12
- scitex/db/_sqlite3/_SQLite3Mixins/_ArrayMixin_v01-need-_hash-col.py +19 -14
- scitex/db/_sqlite3/_SQLite3Mixins/_BatchMixin.py +3 -1
- scitex/db/_sqlite3/_SQLite3Mixins/_BlobMixin.py +7 -7
- scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin.py +118 -111
- scitex/db/_sqlite3/_SQLite3Mixins/_ConnectionMixin.py +8 -10
- scitex/db/_sqlite3/_SQLite3Mixins/_GitMixin.py +17 -45
- scitex/db/_sqlite3/_SQLite3Mixins/_ImportExportMixin.py +1 -3
- scitex/db/_sqlite3/_SQLite3Mixins/_IndexMixin.py +3 -1
- scitex/db/_sqlite3/_SQLite3Mixins/_QueryMixin.py +3 -4
- scitex/db/_sqlite3/_SQLite3Mixins/_RowMixin.py +9 -9
- scitex/db/_sqlite3/_SQLite3Mixins/_TableMixin.py +18 -11
- scitex/db/_sqlite3/_SQLite3Mixins/__init__.py +1 -0
- scitex/db/_sqlite3/__init__.py +1 -1
- scitex/db/_sqlite3/_delete_duplicates.py +13 -11
- scitex/decorators/__init__.py +29 -4
- scitex/decorators/_auto_order.py +43 -43
- scitex/decorators/_batch_fn.py +12 -6
- scitex/decorators/_cache_disk.py +8 -9
- scitex/decorators/_cache_disk_async.py +8 -7
- scitex/decorators/_combined.py +19 -13
- scitex/decorators/_converters.py +16 -3
- scitex/decorators/_deprecated.py +32 -22
- scitex/decorators/_numpy_fn.py +18 -4
- scitex/decorators/_pandas_fn.py +17 -5
- scitex/decorators/_signal_fn.py +17 -3
- scitex/decorators/_torch_fn.py +32 -15
- scitex/decorators/_xarray_fn.py +23 -9
- scitex/dev/_analyze_code_flow.py +0 -2
- scitex/dev/plt/__init__.py +272 -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/dict/_DotDict.py +15 -19
- scitex/dict/_flatten.py +1 -0
- scitex/dict/_listed_dict.py +1 -0
- scitex/dict/_pop_keys.py +1 -0
- scitex/dict/_replace.py +1 -0
- scitex/dict/_safe_merge.py +1 -0
- scitex/dict/_to_str.py +2 -3
- scitex/dsp/__init__.py +13 -4
- scitex/dsp/_crop.py +3 -1
- scitex/dsp/_detect_ripples.py +3 -1
- scitex/dsp/_modulation_index.py +3 -1
- scitex/dsp/_time.py +3 -1
- scitex/dsp/_wavelet.py +0 -1
- scitex/dsp/example.py +0 -5
- scitex/dsp/filt.py +4 -0
- scitex/dsp/utils/__init__.py +4 -1
- scitex/dsp/utils/pac.py +3 -3
- scitex/dt/_normalize_timestamp.py +4 -1
- scitex/errors.py +3 -6
- scitex/etc/__init__.py +1 -1
- scitex/fig/__init__.py +352 -0
- scitex/{vis → fig}/backend/__init__.py +3 -3
- scitex/{vis/backend/export.py → fig/backend/_export.py} +1 -1
- scitex/{vis/backend/parser.py → fig/backend/_parser.py} +2 -4
- scitex/{vis/backend/render.py → fig/backend/_render.py} +1 -1
- scitex/{vis → fig}/canvas.py +16 -4
- scitex/{vis → fig}/editor/__init__.py +0 -0
- scitex/{vis → fig}/editor/_dearpygui_editor.py +450 -304
- scitex/fig/editor/_defaults.py +300 -0
- scitex/fig/editor/_edit.py +751 -0
- scitex/{vis → fig}/editor/_flask_editor.py +8 -8
- scitex/{vis → fig}/editor/_mpl_editor.py +63 -48
- scitex/{vis → fig}/editor/_qt_editor.py +391 -160
- scitex/{vis → fig}/editor/_tkinter_editor.py +146 -89
- scitex/fig/editor/flask_editor/__init__.py +21 -0
- scitex/fig/editor/flask_editor/_bbox.py +1276 -0
- scitex/fig/editor/flask_editor/_core.py +624 -0
- scitex/fig/editor/flask_editor/_plotter.py +601 -0
- scitex/fig/editor/flask_editor/_renderer.py +739 -0
- scitex/{vis/editor/flask_editor/utils.py → fig/editor/flask_editor/_utils.py} +13 -14
- scitex/{vis → fig}/editor/flask_editor/templates/__init__.py +6 -6
- scitex/fig/editor/flask_editor/templates/_html.py +834 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +3136 -0
- scitex/fig/editor/flask_editor/templates/_styles.py +1346 -0
- scitex/{vis → fig}/io/__init__.py +18 -6
- scitex/fig/io/_bundle.py +973 -0
- scitex/{vis/io/canvas.py → fig/io/_canvas.py} +9 -5
- scitex/{vis/io/data.py → fig/io/_data.py} +14 -10
- scitex/{vis/io/directory.py → fig/io/_directory.py} +7 -4
- scitex/{vis/io/export.py → fig/io/_export.py} +16 -13
- scitex/{vis/io/load.py → fig/io/_load.py} +2 -2
- scitex/{vis/io/panel.py → fig/io/_panel.py} +22 -14
- scitex/{vis/io/save.py → fig/io/_save.py} +1 -1
- scitex/{vis → fig}/model/__init__.py +8 -8
- scitex/{vis/model/annotations.py → fig/model/_annotations.py} +3 -5
- scitex/{vis/model/axes.py → fig/model/_axes.py} +2 -2
- scitex/{vis/model/figure.py → fig/model/_figure.py} +1 -1
- scitex/{vis/model/guides.py → fig/model/_guides.py} +2 -2
- scitex/{vis/model/plot.py → fig/model/_plot.py} +3 -5
- scitex/{vis/model/plot_types.py → fig/model/_plot_types.py} +0 -0
- scitex/{vis/model/styles.py → fig/model/_styles.py} +1 -1
- scitex/{vis → fig}/utils/__init__.py +3 -3
- scitex/{vis/utils/defaults.py → fig/utils/_defaults.py} +1 -2
- scitex/{vis/utils/validate.py → fig/utils/_validate.py} +3 -9
- scitex/gen/_DimHandler.py +6 -6
- scitex/gen/__init__.py +5 -1
- scitex/gen/_deprecated_close.py +1 -0
- scitex/gen/_deprecated_start.py +5 -3
- scitex/gen/_detect_environment.py +44 -41
- scitex/gen/_detect_notebook_path.py +51 -47
- scitex/gen/_embed.py +1 -1
- scitex/gen/_get_notebook_path.py +81 -62
- scitex/gen/_inspect_module.py +0 -1
- scitex/gen/_norm.py +16 -7
- scitex/gen/_norm_cache.py +78 -65
- scitex/gen/_print_config.py +0 -3
- scitex/gen/_src.py +2 -3
- scitex/gen/_title_case.py +3 -2
- scitex/gen/_to_even.py +8 -8
- scitex/gen/_transpose.py +3 -3
- scitex/gen/misc.py +0 -3
- scitex/gists/_SigMacro_processFigure_S.py +2 -2
- scitex/gists/_SigMacro_toBlue.py +2 -2
- scitex/gists/__init__.py +4 -1
- scitex/git/_branch.py +19 -11
- scitex/git/_clone.py +23 -15
- scitex/git/_commit.py +10 -12
- scitex/git/_init.py +15 -38
- scitex/git/_remote.py +9 -3
- scitex/git/_result.py +3 -0
- scitex/git/_retry.py +2 -5
- scitex/git/_types.py +4 -0
- scitex/git/_validation.py +8 -8
- scitex/git/_workflow.py +4 -4
- scitex/io/__init__.py +12 -27
- scitex/io/_bundle.py +434 -0
- scitex/io/_flush.py +5 -2
- scitex/io/_glob.py +2 -2
- scitex/io/_json2md.py +3 -3
- scitex/io/_load.py +104 -8
- scitex/io/_load_cache.py +71 -71
- scitex/io/_load_configs.py +2 -3
- scitex/io/_load_modules/_H5Explorer.py +11 -14
- scitex/io/_load_modules/_ZarrExplorer.py +3 -3
- scitex/io/_load_modules/_bibtex.py +62 -63
- scitex/io/_load_modules/_canvas.py +6 -11
- scitex/io/_load_modules/_catboost.py +7 -2
- scitex/io/_load_modules/_hdf5.py +2 -0
- scitex/io/_load_modules/_image.py +7 -4
- scitex/io/_load_modules/_matlab.py +3 -1
- scitex/io/_load_modules/_optuna.py +0 -1
- scitex/io/_load_modules/_pdf.py +38 -29
- scitex/io/_load_modules/_sqlite3.py +1 -0
- scitex/io/_load_modules/_txt.py +6 -2
- scitex/io/_load_modules/_xml.py +9 -9
- scitex/io/_load_modules/_zarr.py +12 -10
- scitex/io/_metadata.py +34 -285
- 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/_qr_utils.py +21 -14
- scitex/io/_save.py +755 -80
- scitex/io/_save_modules/__init__.py +7 -2
- scitex/io/_save_modules/_bibtex.py +66 -61
- scitex/io/_save_modules/_canvas.py +8 -9
- scitex/io/_save_modules/_catboost.py +2 -2
- scitex/io/_save_modules/_csv.py +4 -4
- scitex/io/_save_modules/_excel.py +5 -9
- scitex/io/_save_modules/_hdf5.py +9 -21
- scitex/io/_save_modules/_html.py +5 -5
- scitex/io/_save_modules/_image.py +107 -14
- scitex/io/_save_modules/_joblib.py +2 -2
- scitex/io/_save_modules/_json.py +51 -6
- scitex/io/_save_modules/_listed_dfs_as_csv.py +2 -1
- scitex/io/_save_modules/_listed_scalars_as_csv.py +2 -1
- scitex/io/_save_modules/_matlab.py +2 -2
- scitex/io/_save_modules/_numpy.py +6 -8
- scitex/io/_save_modules/_pickle.py +4 -4
- scitex/io/_save_modules/_plotly.py +3 -3
- scitex/io/_save_modules/_tex.py +30 -29
- scitex/io/_save_modules/_text.py +2 -2
- scitex/io/_save_modules/_yaml.py +9 -9
- scitex/io/_save_modules/_zarr.py +15 -15
- scitex/io/utils/__init__.py +2 -1
- scitex/io/utils/h5_to_zarr.py +183 -163
- scitex/linalg/__init__.py +1 -1
- scitex/linalg/_geometric_median.py +4 -3
- scitex/logging/_Tee.py +5 -7
- scitex/logging/__init__.py +18 -19
- scitex/logging/_config.py +4 -1
- scitex/logging/_context.py +6 -5
- scitex/logging/_formatters.py +2 -3
- scitex/logging/_handlers.py +19 -20
- scitex/logging/_levels.py +9 -17
- scitex/logging/_logger.py +74 -15
- scitex/logging/_print_capture.py +17 -17
- 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/_BNet.py +1 -3
- scitex/nn/_Filters.py +6 -2
- scitex/nn/_ModulationIndex.py +3 -1
- scitex/nn/_PAC.py +3 -2
- scitex/nn/_PSD.py +0 -1
- scitex/nn/__init__.py +16 -3
- scitex/path/_clean.py +10 -8
- scitex/path/_find.py +1 -1
- scitex/path/_get_spath.py +1 -2
- scitex/path/_mk_spath.py +1 -1
- scitex/path/_symlink.py +5 -10
- scitex/pd/__init__.py +4 -1
- scitex/pd/_force_df.py +24 -24
- scitex/pd/_get_unique.py +1 -0
- scitex/pd/_merge_columns.py +1 -1
- scitex/pd/_round.py +11 -7
- scitex/pd/_to_xy.py +0 -1
- scitex/plt/__init__.py +190 -89
- scitex/plt/_subplots/_AxesWrapper.py +28 -12
- scitex/plt/_subplots/_AxisWrapper.py +114 -47
- 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 +59 -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 +26 -14
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +80 -73
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +79 -5
- scitex/plt/_subplots/_FigWrapper.py +97 -64
- scitex/plt/_subplots/_SubplotsWrapper.py +161 -84
- scitex/plt/_subplots/__init__.py +10 -0
- scitex/plt/_subplots/_export_as_csv.py +124 -52
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +9 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +14 -23
- scitex/plt/_subplots/_export_as_csv_formatters/_format_bar.py +88 -38
- scitex/plt/_subplots/_export_as_csv_formatters/_format_barh.py +25 -31
- scitex/plt/_subplots/_export_as_csv_formatters/_format_boxplot.py +53 -23
- scitex/plt/_subplots/_export_as_csv_formatters/_format_contour.py +38 -25
- scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +17 -9
- scitex/plt/_subplots/_export_as_csv_formatters/_format_errorbar.py +70 -124
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +30 -17
- scitex/plt/_subplots/_export_as_csv_formatters/_format_fill.py +31 -17
- scitex/plt/_subplots/_export_as_csv_formatters/_format_fill_between.py +33 -21
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +14 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hist.py +43 -29
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +14 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +27 -11
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +34 -16
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +16 -8
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +15 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +85 -46
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +52 -27
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +14 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +27 -18
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +14 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +16 -8
- scitex/plt/_subplots/_export_as_csv_formatters/_format_scatter.py +17 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_barplot.py +43 -26
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_boxplot.py +68 -47
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_heatmap.py +52 -64
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_histplot.py +55 -50
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +23 -10
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_kdeplot.py +63 -29
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +48 -40
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +20 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_scatterplot.py +44 -40
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_stripplot.py +46 -39
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_swarmplot.py +46 -39
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_violinplot.py +75 -94
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +12 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +12 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +17 -9
- 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 +31 -18
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +24 -11
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_fillv.py +35 -31
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +33 -23
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +44 -28
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +31 -12
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_line.py +34 -23
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +32 -26
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +29 -23
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +32 -26
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +21 -11
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_rectangle.py +84 -56
- 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 +46 -34
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_shaded_line.py +46 -30
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_violin.py +51 -51
- scitex/plt/_subplots/_export_as_csv_formatters/_format_text.py +32 -31
- scitex/plt/_subplots/_export_as_csv_formatters/_format_violin.py +34 -31
- scitex/plt/_subplots/_export_as_csv_formatters/_format_violinplot.py +44 -37
- scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +91 -74
- scitex/plt/_tpl.py +6 -5
- scitex/plt/ax/_plot/__init__.py +24 -0
- scitex/plt/ax/_plot/_add_fitted_line.py +12 -11
- scitex/plt/ax/_plot/_plot_circular_hist.py +3 -1
- scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +25 -19
- scitex/plt/ax/_plot/_stx_conf_mat.py +6 -3
- scitex/plt/ax/_plot/_stx_ecdf.py +9 -5
- scitex/plt/ax/_plot/_stx_fillv.py +4 -2
- scitex/plt/ax/_plot/_stx_heatmap.py +7 -4
- scitex/plt/ax/_plot/_stx_image.py +7 -5
- scitex/plt/ax/_plot/_stx_joyplot.py +32 -10
- scitex/plt/ax/_plot/_stx_raster.py +26 -11
- scitex/plt/ax/_plot/_stx_rectangle.py +2 -2
- scitex/plt/ax/_plot/_stx_shaded_line.py +15 -11
- scitex/plt/ax/_plot/_stx_violin.py +3 -1
- scitex/plt/ax/_style/_add_marginal_ax.py +6 -4
- scitex/plt/ax/_style/_auto_scale_axis.py +14 -10
- scitex/plt/ax/_style/_extend.py +3 -1
- scitex/plt/ax/_style/_force_aspect.py +5 -3
- scitex/plt/ax/_style/_format_units.py +2 -2
- scitex/plt/ax/_style/_hide_spines.py +5 -1
- scitex/plt/ax/_style/_map_ticks.py +5 -3
- scitex/plt/ax/_style/_rotate_labels.py +5 -4
- scitex/plt/ax/_style/_rotate_labels_v01.py +73 -63
- scitex/plt/ax/_style/_set_log_scale.py +120 -85
- scitex/plt/ax/_style/_set_meta.py +99 -76
- scitex/plt/ax/_style/_set_supxyt.py +33 -16
- scitex/plt/ax/_style/_set_xyt.py +27 -18
- scitex/plt/ax/_style/_share_axes.py +15 -5
- scitex/plt/ax/_style/_show_spines.py +58 -57
- scitex/plt/ax/_style/_style_barplot.py +1 -1
- scitex/plt/ax/_style/_style_boxplot.py +25 -14
- scitex/plt/ax/_style/_style_errorbar.py +0 -0
- scitex/plt/ax/_style/_style_scatter.py +1 -1
- scitex/plt/ax/_style/_style_suptitles.py +3 -3
- scitex/plt/ax/_style/_style_violinplot.py +8 -2
- scitex/plt/color/__init__.py +34 -2
- scitex/plt/color/_add_hue_col.py +1 -0
- scitex/plt/color/_colors.py +0 -1
- scitex/plt/color/_get_colors_from_conf_matap.py +3 -1
- scitex/plt/color/_vizualize_colors.py +0 -1
- scitex/plt/docs/FIGURE_ARCHITECTURE.md +155 -97
- scitex/plt/gallery/README.md +75 -0
- scitex/plt/gallery/__init__.py +29 -0
- scitex/plt/gallery/_generate.py +560 -0
- scitex/plt/gallery/_plots.py +594 -0
- scitex/plt/gallery/_registry.py +153 -0
- scitex/plt/io/__init__.py +53 -0
- scitex/plt/io/_bundle.py +490 -0
- scitex/plt/io/_layered_bundle.py +1343 -0
- scitex/plt/styles/SCITEX_STYLE.yaml +26 -0
- scitex/plt/styles/__init__.py +23 -9
- scitex/plt/styles/_plot_defaults.py +62 -61
- scitex/plt/styles/_plot_postprocess.py +126 -77
- scitex/plt/styles/_style_loader.py +0 -0
- scitex/plt/styles/presets.py +121 -18
- scitex/plt/utils/__init__.py +42 -3
- scitex/plt/utils/_close.py +8 -3
- scitex/plt/utils/_collect_figure_metadata.py +3033 -271
- scitex/plt/utils/_colorbar.py +15 -17
- scitex/plt/utils/_configure_mpl.py +26 -30
- scitex/plt/utils/_crop.py +87 -36
- scitex/plt/utils/_csv_column_naming.py +177 -72
- scitex/plt/utils/_dimension_viewer.py +7 -19
- scitex/plt/utils/_figure_from_axes_mm.py +70 -16
- scitex/plt/utils/_figure_mm.py +119 -3
- scitex/plt/utils/_get_actual_font.py +5 -4
- scitex/plt/utils/_histogram_utils.py +52 -48
- scitex/plt/utils/_hitmap.py +1643 -0
- scitex/plt/utils/_is_valid_axis.py +19 -13
- scitex/plt/utils/_mk_colorbar.py +3 -3
- scitex/plt/utils/_scientific_captions.py +202 -139
- scitex/plt/utils/_scitex_config.py +98 -98
- scitex/plt/utils/_units.py +0 -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/_RandomStateManager.py +33 -38
- scitex/repro/__init__.py +16 -7
- scitex/repro/_gen_ID.py +7 -9
- scitex/repro/_gen_timestamp.py +7 -6
- scitex/repro/_hash_array.py +8 -12
- scitex/reproduce/__init__.py +1 -1
- scitex/resource/_get_processor_usages.py +3 -1
- scitex/resource/_log_processor_usages.py +3 -1
- scitex/rng/__init__.py +1 -1
- scitex/schema/README.md +178 -0
- scitex/schema/__init__.py +237 -0
- scitex/schema/_canvas.py +444 -0
- scitex/schema/_plot.py +1015 -0
- scitex/schema/_stats.py +762 -0
- scitex/schema/_validation.py +590 -0
- scitex/scholar/.legacy/Scholar.py +5 -12
- scitex/scholar/.legacy/_Scholar.py +66 -99
- scitex/scholar/.legacy/_ScholarAPI.py +75 -66
- scitex/scholar/.legacy/_tmp/search_engine/_BaseSearchEngine.py +3 -3
- scitex/scholar/.legacy/_tmp/search_engine/_UnifiedSearcher.py +4 -9
- scitex/scholar/.legacy/_tmp/search_engine/__init__.py +14 -21
- scitex/scholar/.legacy/_tmp/search_engine/local/_LocalSearchEngine.py +40 -37
- scitex/scholar/.legacy/_tmp/search_engine/local/_VectorSearchEngine.py +31 -28
- scitex/scholar/.legacy/_tmp/search_engine/web/_ArxivSearchEngine.py +74 -65
- scitex/scholar/.legacy/_tmp/search_engine/web/_CrossRefSearchEngine.py +122 -116
- scitex/scholar/.legacy/_tmp/search_engine/web/_GoogleScholarSearchEngine.py +65 -59
- scitex/scholar/.legacy/_tmp/search_engine/web/_PubMedSearchEngine.py +121 -107
- scitex/scholar/.legacy/_tmp/search_engine/web/_SemanticScholarSearchEngine.py +5 -12
- scitex/scholar/.legacy/database/_DatabaseEntry.py +49 -45
- scitex/scholar/.legacy/database/_DatabaseIndex.py +131 -94
- scitex/scholar/.legacy/database/_LibraryManager.py +65 -63
- scitex/scholar/.legacy/database/_PaperDatabase.py +138 -124
- scitex/scholar/.legacy/database/_ScholarDatabaseIntegration.py +14 -36
- scitex/scholar/.legacy/database/_StorageIntegratedDB.py +192 -156
- scitex/scholar/.legacy/database/_ZoteroCompatibleDB.py +300 -237
- scitex/scholar/.legacy/database/__init__.py +2 -1
- scitex/scholar/.legacy/database/manage.py +92 -84
- scitex/scholar/.legacy/lookup/_LookupIndex.py +157 -101
- scitex/scholar/.legacy/lookup/__init__.py +2 -1
- scitex/scholar/.legacy/metadata/doi/batch/_MetadataHandlerForBatchDOIResolution.py +4 -9
- scitex/scholar/.legacy/metadata/doi/batch/_ProgressManagerForBatchDOIResolution.py +10 -23
- scitex/scholar/.legacy/metadata/doi/batch/_SourceStatsManagerForBatchDOIResolution.py +4 -9
- scitex/scholar/.legacy/metadata/doi/batch/__init__.py +3 -1
- scitex/scholar/.legacy/metadata/doi/resolvers/_BatchDOIResolver.py +10 -25
- scitex/scholar/.legacy/metadata/doi/resolvers/_BibTeXDOIResolver.py +19 -49
- scitex/scholar/.legacy/metadata/doi/resolvers/_DOIResolver.py +1 -0
- scitex/scholar/.legacy/metadata/doi/resolvers/_SingleDOIResolver.py +8 -20
- scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
- scitex/scholar/.legacy/metadata/doi/sources/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +49 -37
- scitex/scholar/.legacy/metadata/doi/sources/_ArXivSource.py +11 -30
- scitex/scholar/.legacy/metadata/doi/sources/_BaseDOISource.py +19 -47
- scitex/scholar/.legacy/metadata/doi/sources/_CrossRefLocalSource.py +1 -0
- scitex/scholar/.legacy/metadata/doi/sources/_CrossRefSource.py +12 -33
- scitex/scholar/.legacy/metadata/doi/sources/_OpenAlexSource.py +8 -20
- scitex/scholar/.legacy/metadata/doi/sources/_PubMedSource.py +10 -27
- scitex/scholar/.legacy/metadata/doi/sources/_SemanticScholarSource.py +11 -29
- scitex/scholar/.legacy/metadata/doi/sources/_SourceManager.py +8 -21
- scitex/scholar/.legacy/metadata/doi/sources/_SourceResolutionStrategy.py +24 -55
- scitex/scholar/.legacy/metadata/doi/sources/_SourceRotationManager.py +8 -21
- scitex/scholar/.legacy/metadata/doi/sources/_URLDOISource.py +9 -16
- scitex/scholar/.legacy/metadata/doi/sources/_UnifiedSource.py +8 -22
- scitex/scholar/.legacy/metadata/doi/sources/__init__.py +1 -0
- scitex/scholar/.legacy/metadata/doi/utils/_PubMedConverter.py +4 -8
- scitex/scholar/.legacy/metadata/doi/utils/_RateLimitHandler.py +17 -43
- scitex/scholar/.legacy/metadata/doi/utils/_TextNormalizer.py +8 -18
- scitex/scholar/.legacy/metadata/doi/utils/_URLDOIExtractor.py +4 -8
- scitex/scholar/.legacy/metadata/doi/utils/__init__.py +1 -0
- scitex/scholar/.legacy/metadata/doi/utils/_to_complete_metadata_structure.py +1 -0
- scitex/scholar/.legacy/metadata/enrichment/_LibraryEnricher.py +2 -3
- scitex/scholar/.legacy/metadata/enrichment/enrichers/_ImpactFactorEnricher.py +6 -12
- scitex/scholar/.legacy/metadata/enrichment/enrichers/_SmartEnricher.py +5 -10
- scitex/scholar/.legacy/metadata/enrichment/sources/_UnifiedMetadataSource.py +4 -5
- scitex/scholar/.legacy/metadata/query_to_full_meta_json.py +8 -12
- scitex/scholar/.legacy/metadata/urls/_URLMetadataHandler.py +3 -3
- scitex/scholar/.legacy/metadata/urls/_ZoteroTranslatorRunner.py +15 -21
- scitex/scholar/.legacy/metadata/urls/__init__.py +3 -3
- scitex/scholar/.legacy/metadata/urls/_finder.py +4 -6
- scitex/scholar/.legacy/metadata/urls/_handler.py +7 -15
- scitex/scholar/.legacy/metadata/urls/_resolver.py +6 -12
- scitex/scholar/.legacy/search/_Embedder.py +74 -69
- scitex/scholar/.legacy/search/_SemanticSearch.py +91 -90
- scitex/scholar/.legacy/search/_SemanticSearchEngine.py +104 -109
- scitex/scholar/.legacy/search/_UnifiedSearcher.py +530 -471
- scitex/scholar/.legacy/search/_VectorDatabase.py +111 -92
- scitex/scholar/.legacy/search/__init__.py +1 -0
- scitex/scholar/.legacy/storage/_EnhancedStorageManager.py +182 -154
- scitex/scholar/.legacy/storage/__init__.py +2 -1
- scitex/scholar/__init__.py +0 -2
- scitex/scholar/__main__.py +1 -3
- scitex/scholar/auth/ScholarAuthManager.py +13 -36
- scitex/scholar/auth/core/AuthenticationGateway.py +15 -29
- scitex/scholar/auth/core/BrowserAuthenticator.py +22 -57
- scitex/scholar/auth/core/StrategyResolver.py +10 -27
- scitex/scholar/auth/core/__init__.py +5 -1
- scitex/scholar/auth/gateway/_OpenURLLinkFinder.py +11 -21
- scitex/scholar/auth/gateway/_OpenURLResolver.py +10 -18
- scitex/scholar/auth/gateway/_resolve_functions.py +3 -3
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -0
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +7 -14
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +29 -57
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +87 -73
- scitex/scholar/auth/session/AuthCacheManager.py +12 -22
- scitex/scholar/auth/session/SessionManager.py +4 -6
- scitex/scholar/auth/sso/BaseSSOAutomator.py +13 -19
- scitex/scholar/auth/sso/OpenAthensSSOAutomator.py +16 -45
- scitex/scholar/auth/sso/SSOAutomator.py +8 -15
- scitex/scholar/auth/sso/UniversityOfMelbourneSSOAutomator.py +13 -23
- scitex/scholar/browser/ScholarBrowserManager.py +31 -56
- scitex/scholar/browser/__init__.py +1 -0
- scitex/scholar/browser/utils/click_and_wait.py +3 -4
- scitex/scholar/browser/utils/close_unwanted_pages.py +4 -7
- scitex/scholar/browser/utils/wait_redirects.py +15 -40
- scitex/scholar/citation_graph/__init__.py +0 -0
- scitex/scholar/citation_graph/builder.py +3 -7
- scitex/scholar/citation_graph/database.py +4 -11
- scitex/scholar/citation_graph/example.py +5 -10
- scitex/scholar/citation_graph/models.py +0 -0
- scitex/scholar/cli/_url_utils.py +1 -1
- scitex/scholar/cli/chrome.py +5 -3
- scitex/scholar/cli/download_pdf.py +13 -14
- scitex/scholar/cli/handlers/bibtex_handler.py +4 -12
- scitex/scholar/cli/handlers/doi_handler.py +1 -3
- scitex/scholar/cli/handlers/project_handler.py +6 -20
- scitex/scholar/cli/open_browser.py +41 -39
- scitex/scholar/cli/open_browser_auto.py +31 -39
- scitex/scholar/cli/open_browser_monitored.py +27 -24
- scitex/scholar/config/ScholarConfig.py +5 -8
- scitex/scholar/config/__init__.py +1 -0
- scitex/scholar/config/core/_CascadeConfig.py +3 -3
- scitex/scholar/config/core/_PathManager.py +16 -28
- scitex/scholar/core/Paper.py +79 -78
- scitex/scholar/core/Papers.py +16 -27
- scitex/scholar/core/Scholar.py +98 -229
- scitex/scholar/core/journal_normalizer.py +52 -49
- scitex/scholar/core/oa_cache.py +27 -23
- scitex/scholar/core/open_access.py +17 -8
- scitex/scholar/docs/template.py +4 -3
- scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/clf_svm.py +0 -0
- scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/download.py +0 -0
- scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_conf_mat.py +0 -0
- scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_digits.py +0 -0
- scitex/scholar/docs/to_claude/examples/example-python-project-scitex/scripts/mnist/plot_umap_space.py +0 -0
- scitex/scholar/examples/00_config.py +10 -9
- scitex/scholar/examples/01_auth.py +3 -0
- scitex/scholar/examples/02_browser.py +14 -10
- scitex/scholar/examples/03_01-engine.py +3 -0
- scitex/scholar/examples/03_02-engine-for-bibtex.py +4 -3
- scitex/scholar/examples/04_01-url.py +9 -9
- scitex/scholar/examples/04_02-url-for-bibtex.py +7 -3
- scitex/scholar/examples/04_02-url-for-dois.py +87 -97
- scitex/scholar/examples/05_download_pdf.py +10 -4
- scitex/scholar/examples/06_find_and_download.py +6 -6
- scitex/scholar/examples/06_parse_bibtex.py +17 -17
- scitex/scholar/examples/07_storage_integration.py +6 -9
- scitex/scholar/examples/99_fullpipeline-for-bibtex.py +14 -15
- scitex/scholar/examples/99_fullpipeline-for-one-entry.py +31 -23
- scitex/scholar/examples/99_maintenance.py +3 -0
- scitex/scholar/examples/dev.py +2 -3
- scitex/scholar/examples/zotero_integration.py +11 -18
- scitex/scholar/impact_factor/ImpactFactorEngine.py +7 -9
- scitex/scholar/impact_factor/estimation/__init__.py +4 -4
- scitex/scholar/impact_factor/estimation/core/__init__.py +3 -7
- scitex/scholar/impact_factor/estimation/core/cache_manager.py +223 -211
- scitex/scholar/impact_factor/estimation/core/calculator.py +165 -131
- scitex/scholar/impact_factor/estimation/core/journal_matcher.py +217 -172
- scitex/scholar/impact_factor/jcr/ImpactFactorJCREngine.py +6 -14
- scitex/scholar/impact_factor/jcr/build_database.py +4 -3
- scitex/scholar/integration/base.py +9 -17
- scitex/scholar/integration/mendeley/exporter.py +2 -4
- scitex/scholar/integration/mendeley/importer.py +3 -3
- scitex/scholar/integration/mendeley/linker.py +3 -3
- scitex/scholar/integration/mendeley/mapper.py +9 -6
- scitex/scholar/integration/zotero/__main__.py +26 -43
- scitex/scholar/integration/zotero/exporter.py +15 -11
- scitex/scholar/integration/zotero/importer.py +12 -10
- scitex/scholar/integration/zotero/linker.py +8 -12
- scitex/scholar/integration/zotero/mapper.py +17 -12
- scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSource.py +37 -35
- scitex/scholar/metadata_engines/.combined-SemanticScholarSource/_SemanticScholarSourceEnhanced.py +47 -35
- scitex/scholar/metadata_engines/ScholarEngine.py +21 -43
- scitex/scholar/metadata_engines/__init__.py +1 -0
- scitex/scholar/metadata_engines/individual/ArXivEngine.py +15 -37
- scitex/scholar/metadata_engines/individual/CrossRefEngine.py +15 -42
- scitex/scholar/metadata_engines/individual/CrossRefLocalEngine.py +24 -45
- scitex/scholar/metadata_engines/individual/OpenAlexEngine.py +11 -21
- scitex/scholar/metadata_engines/individual/PubMedEngine.py +10 -27
- scitex/scholar/metadata_engines/individual/SemanticScholarEngine.py +28 -35
- scitex/scholar/metadata_engines/individual/URLDOIEngine.py +11 -22
- scitex/scholar/metadata_engines/individual/_BaseDOIEngine.py +20 -49
- scitex/scholar/metadata_engines/utils/_PubMedConverter.py +4 -8
- scitex/scholar/metadata_engines/utils/_URLDOIExtractor.py +5 -10
- scitex/scholar/metadata_engines/utils/__init__.py +2 -0
- scitex/scholar/metadata_engines/utils/_metadata2bibtex.py +3 -0
- scitex/scholar/metadata_engines/utils/_standardize_metadata.py +2 -3
- scitex/scholar/pdf_download/ScholarPDFDownloader.py +25 -37
- scitex/scholar/pdf_download/strategies/chrome_pdf_viewer.py +11 -19
- scitex/scholar/pdf_download/strategies/direct_download.py +5 -9
- scitex/scholar/pdf_download/strategies/manual_download_fallback.py +3 -3
- scitex/scholar/pdf_download/strategies/manual_download_utils.py +6 -13
- scitex/scholar/pdf_download/strategies/open_access_download.py +49 -31
- scitex/scholar/pdf_download/strategies/response_body.py +8 -19
- scitex/scholar/pipelines/ScholarPipelineBibTeX.py +9 -18
- scitex/scholar/pipelines/ScholarPipelineMetadataParallel.py +25 -26
- scitex/scholar/pipelines/ScholarPipelineMetadataSingle.py +62 -23
- scitex/scholar/pipelines/ScholarPipelineParallel.py +13 -30
- scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +299 -220
- scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +202 -165
- scitex/scholar/pipelines/ScholarPipelineSingle.py +25 -51
- scitex/scholar/pipelines/SearchQueryParser.py +55 -55
- scitex/scholar/search_engines/ScholarSearchEngine.py +31 -27
- scitex/scholar/search_engines/_BaseSearchEngine.py +20 -23
- scitex/scholar/search_engines/individual/ArXivSearchEngine.py +53 -35
- scitex/scholar/search_engines/individual/CrossRefSearchEngine.py +47 -40
- scitex/scholar/search_engines/individual/OpenAlexSearchEngine.py +55 -50
- scitex/scholar/search_engines/individual/PubMedSearchEngine.py +8 -10
- scitex/scholar/search_engines/individual/SemanticScholarSearchEngine.py +55 -49
- scitex/scholar/storage/BibTeXHandler.py +150 -95
- scitex/scholar/storage/PaperIO.py +3 -6
- scitex/scholar/storage/ScholarLibrary.py +70 -49
- scitex/scholar/storage/_DeduplicationManager.py +52 -25
- scitex/scholar/storage/_LibraryCacheManager.py +19 -46
- scitex/scholar/storage/_LibraryManager.py +65 -175
- scitex/scholar/url_finder/ScholarURLFinder.py +9 -25
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_direct_links.py +1 -1
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_href.py +6 -10
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_navigation.py +4 -6
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_publisher_patterns.py +8 -15
- scitex/scholar/url_finder/strategies/find_pdf_urls_by_zotero_translators.py +3 -3
- scitex/scholar/url_finder/strategies/find_supplementary_urls_by_href.py +3 -3
- scitex/scholar/url_finder/translators/core/patterns.py +6 -4
- scitex/scholar/url_finder/translators/core/registry.py +6 -9
- scitex/scholar/url_finder/translators/individual/BOFiP_Impots.py +60 -52
- scitex/scholar/url_finder/translators/individual/Baidu_Scholar.py +54 -62
- scitex/scholar/url_finder/translators/individual/Bangkok_Post.py +38 -44
- scitex/scholar/url_finder/translators/individual/Baruch_Foundation.py +43 -47
- scitex/scholar/url_finder/translators/individual/Beobachter.py +46 -50
- scitex/scholar/url_finder/translators/individual/Bezneng_Gajit.py +37 -41
- scitex/scholar/url_finder/translators/individual/BibLaTeX.py +59 -52
- scitex/scholar/url_finder/translators/individual/BibTeX.py +83 -79
- scitex/scholar/url_finder/translators/individual/Biblio_com.py +48 -51
- scitex/scholar/url_finder/translators/individual/Bibliontology_RDF.py +58 -56
- scitex/scholar/url_finder/translators/individual/Camara_Brasileira_do_Livro_ISBN.py +102 -99
- scitex/scholar/url_finder/translators/individual/CanLII.py +49 -43
- scitex/scholar/url_finder/translators/individual/Canada_com.py +36 -40
- scitex/scholar/url_finder/translators/individual/Canadian_Letters_and_Images.py +43 -43
- scitex/scholar/url_finder/translators/individual/Canadiana_ca.py +77 -66
- scitex/scholar/url_finder/translators/individual/Cascadilla_Proceedings_Project.py +68 -62
- scitex/scholar/url_finder/translators/individual/Central_and_Eastern_European_Online_Library_Journals.py +60 -60
- scitex/scholar/url_finder/translators/individual/Champlain_Society_Collection.py +63 -61
- scitex/scholar/url_finder/translators/individual/Chicago_Journal_of_Theoretical_Computer_Science.py +74 -58
- scitex/scholar/url_finder/translators/individual/Christian_Science_Monitor.py +32 -38
- scitex/scholar/url_finder/translators/individual/Columbia_University_Press.py +51 -47
- scitex/scholar/url_finder/translators/individual/Common_Place.py +66 -57
- scitex/scholar/url_finder/translators/individual/Cornell_LII.py +66 -62
- scitex/scholar/url_finder/translators/individual/Cornell_University_Press.py +38 -45
- scitex/scholar/url_finder/translators/individual/CourtListener.py +52 -56
- scitex/scholar/url_finder/translators/individual/DAI_Zenon.py +53 -54
- scitex/scholar/url_finder/translators/individual/access_medicine.py +27 -33
- scitex/scholar/url_finder/translators/individual/acm.py +1 -1
- scitex/scholar/url_finder/translators/individual/acm_digital_library.py +93 -63
- scitex/scholar/url_finder/translators/individual/airiti.py +3 -1
- scitex/scholar/url_finder/translators/individual/aosic.py +3 -1
- scitex/scholar/url_finder/translators/individual/archive_ouverte_aosic.py +3 -1
- scitex/scholar/url_finder/translators/individual/archive_ouverte_en_sciences_de_l_information_et_de_la_communication___aosic_.py +6 -2
- scitex/scholar/url_finder/translators/individual/artforum.py +35 -27
- scitex/scholar/url_finder/translators/individual/arxiv.py +1 -1
- scitex/scholar/url_finder/translators/individual/arxiv_org.py +8 -4
- scitex/scholar/url_finder/translators/individual/atlanta_journal_constitution.py +22 -18
- scitex/scholar/url_finder/translators/individual/atypon_journals.py +19 -11
- scitex/scholar/url_finder/translators/individual/austlii_and_nzlii.py +48 -44
- scitex/scholar/url_finder/translators/individual/australian_dictionary_of_biography.py +21 -17
- scitex/scholar/url_finder/translators/individual/bailii.py +22 -19
- scitex/scholar/url_finder/translators/individual/bbc.py +46 -42
- scitex/scholar/url_finder/translators/individual/bbc_genome.py +37 -25
- scitex/scholar/url_finder/translators/individual/biblioteca_nacional_de_maestros.py +24 -20
- scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationale_quebec_pistard.py +42 -43
- scitex/scholar/url_finder/translators/individual/bibliotheque_archives_nationales_quebec.py +87 -81
- scitex/scholar/url_finder/translators/individual/bibliotheque_nationale_france.py +39 -37
- scitex/scholar/url_finder/translators/individual/bibsys.py +32 -28
- scitex/scholar/url_finder/translators/individual/bioconductor.py +58 -52
- scitex/scholar/url_finder/translators/individual/biomed_central.py +23 -15
- scitex/scholar/url_finder/translators/individual/biorxiv.py +26 -13
- scitex/scholar/url_finder/translators/individual/blogger.py +39 -43
- scitex/scholar/url_finder/translators/individual/bloomberg.py +48 -52
- scitex/scholar/url_finder/translators/individual/bloomsbury_food_library.py +37 -37
- scitex/scholar/url_finder/translators/individual/bluesky.py +30 -28
- scitex/scholar/url_finder/translators/individual/bnf_isbn.py +1 -1
- scitex/scholar/url_finder/translators/individual/bocc.py +66 -60
- scitex/scholar/url_finder/translators/individual/boe.py +52 -52
- scitex/scholar/url_finder/translators/individual/brill.py +3 -1
- scitex/scholar/url_finder/translators/individual/business_standard.py +36 -38
- scitex/scholar/url_finder/translators/individual/cabi_cab_abstracts.py +39 -41
- scitex/scholar/url_finder/translators/individual/cambridge.py +3 -1
- scitex/scholar/url_finder/translators/individual/cambridge_core.py +30 -24
- scitex/scholar/url_finder/translators/individual/caod.py +50 -46
- scitex/scholar/url_finder/translators/individual/cbc.py +91 -67
- scitex/scholar/url_finder/translators/individual/ccfr_bnf.py +49 -53
- scitex/scholar/url_finder/translators/individual/cia_world_factbook.py +43 -33
- scitex/scholar/url_finder/translators/individual/crossref_rest.py +208 -174
- scitex/scholar/url_finder/translators/individual/current_affairs.py +29 -35
- scitex/scholar/url_finder/translators/individual/dabi.py +70 -66
- scitex/scholar/url_finder/translators/individual/dagens_nyheter.py +3 -1
- scitex/scholar/url_finder/translators/individual/dagstuhl.py +10 -15
- scitex/scholar/url_finder/translators/individual/dar_almandumah.py +13 -9
- scitex/scholar/url_finder/translators/individual/dart_europe.py +19 -22
- scitex/scholar/url_finder/translators/individual/data_gov.py +2 -2
- scitex/scholar/url_finder/translators/individual/databrary.py +27 -28
- scitex/scholar/url_finder/translators/individual/datacite_json.py +152 -137
- scitex/scholar/url_finder/translators/individual/dataverse.py +68 -64
- scitex/scholar/url_finder/translators/individual/daum_news.py +38 -38
- scitex/scholar/url_finder/translators/individual/dblp.py +4 -8
- scitex/scholar/url_finder/translators/individual/dblp_computer_science_bibliography.py +8 -3
- scitex/scholar/url_finder/translators/individual/dbpia.py +5 -3
- scitex/scholar/url_finder/translators/individual/defense_technical_information_center.py +30 -28
- scitex/scholar/url_finder/translators/individual/delpher.py +102 -79
- scitex/scholar/url_finder/translators/individual/demographic_research.py +35 -31
- scitex/scholar/url_finder/translators/individual/denik_cz.py +58 -54
- scitex/scholar/url_finder/translators/individual/depatisnet.py +7 -10
- scitex/scholar/url_finder/translators/individual/der_freitag.py +81 -66
- scitex/scholar/url_finder/translators/individual/der_spiegel.py +56 -54
- scitex/scholar/url_finder/translators/individual/digibib_net.py +3 -1
- scitex/scholar/url_finder/translators/individual/digizeitschriften.py +3 -1
- scitex/scholar/url_finder/translators/individual/dpla.py +13 -14
- scitex/scholar/url_finder/translators/individual/dspace.py +2 -2
- scitex/scholar/url_finder/translators/individual/ebrary.py +3 -1
- scitex/scholar/url_finder/translators/individual/ebscohost.py +3 -1
- scitex/scholar/url_finder/translators/individual/electronic_colloquium_on_computational_complexity.py +3 -1
- scitex/scholar/url_finder/translators/individual/elife.py +3 -1
- scitex/scholar/url_finder/translators/individual/elsevier_health_journals.py +3 -1
- scitex/scholar/url_finder/translators/individual/emerald.py +3 -1
- scitex/scholar/url_finder/translators/individual/emerald_insight.py +3 -1
- scitex/scholar/url_finder/translators/individual/epicurious.py +3 -1
- scitex/scholar/url_finder/translators/individual/eurogamerusgamer.py +3 -1
- scitex/scholar/url_finder/translators/individual/fachportal_padagogik.py +3 -1
- scitex/scholar/url_finder/translators/individual/frontiers.py +1 -1
- scitex/scholar/url_finder/translators/individual/gale_databases.py +3 -1
- scitex/scholar/url_finder/translators/individual/gms_german_medical_science.py +6 -2
- scitex/scholar/url_finder/translators/individual/ieee_computer_society.py +6 -2
- scitex/scholar/url_finder/translators/individual/ieee_xplore.py +41 -35
- scitex/scholar/url_finder/translators/individual/inter_research_science_center.py +6 -2
- scitex/scholar/url_finder/translators/individual/jisc_historical_texts.py +3 -1
- scitex/scholar/url_finder/translators/individual/jstor.py +14 -12
- scitex/scholar/url_finder/translators/individual/korean_national_library.py +3 -1
- scitex/scholar/url_finder/translators/individual/la_times.py +3 -1
- scitex/scholar/url_finder/translators/individual/landesbibliographie_baden_wurttemberg.py +3 -1
- scitex/scholar/url_finder/translators/individual/legislative_insight.py +3 -1
- scitex/scholar/url_finder/translators/individual/libraries_tasmania.py +3 -1
- scitex/scholar/url_finder/translators/individual/library_catalog__koha_.py +3 -1
- scitex/scholar/url_finder/translators/individual/lingbuzz.py +2 -2
- scitex/scholar/url_finder/translators/individual/max_planck_institute_for_the_history_of_science_virtual_laboratory_library.py +3 -1
- scitex/scholar/url_finder/translators/individual/mdpi.py +12 -6
- scitex/scholar/url_finder/translators/individual/microbiology_society_journals.py +3 -1
- scitex/scholar/url_finder/translators/individual/midas_journals.py +3 -1
- scitex/scholar/url_finder/translators/individual/nagoya_university_opac.py +3 -1
- scitex/scholar/url_finder/translators/individual/nature_publishing_group.py +32 -19
- scitex/scholar/url_finder/translators/individual/ntsb_accident_reports.py +3 -1
- scitex/scholar/url_finder/translators/individual/openedition_journals.py +8 -4
- scitex/scholar/url_finder/translators/individual/orcid.py +16 -15
- scitex/scholar/url_finder/translators/individual/oxford.py +25 -19
- scitex/scholar/url_finder/translators/individual/oxford_dictionaries_premium.py +3 -1
- scitex/scholar/url_finder/translators/individual/ozon_ru.py +3 -1
- scitex/scholar/url_finder/translators/individual/plos.py +9 -12
- scitex/scholar/url_finder/translators/individual/polygon.py +3 -1
- scitex/scholar/url_finder/translators/individual/primo.py +3 -1
- scitex/scholar/url_finder/translators/individual/project_muse.py +3 -1
- scitex/scholar/url_finder/translators/individual/pubfactory_journals.py +3 -1
- scitex/scholar/url_finder/translators/individual/pubmed.py +71 -65
- scitex/scholar/url_finder/translators/individual/pubmed_central.py +8 -6
- scitex/scholar/url_finder/translators/individual/rechtspraak_nl.py +3 -1
- scitex/scholar/url_finder/translators/individual/sage_journals.py +25 -17
- scitex/scholar/url_finder/translators/individual/sciencedirect.py +36 -17
- scitex/scholar/url_finder/translators/individual/semantics_visual_library.py +3 -1
- scitex/scholar/url_finder/translators/individual/silverchair.py +70 -52
- scitex/scholar/url_finder/translators/individual/sora.py +3 -1
- scitex/scholar/url_finder/translators/individual/springer.py +15 -11
- scitex/scholar/url_finder/translators/individual/ssrn.py +3 -3
- scitex/scholar/url_finder/translators/individual/stanford_encyclopedia_of_philosophy.py +3 -1
- scitex/scholar/url_finder/translators/individual/superlib.py +3 -1
- scitex/scholar/url_finder/translators/individual/treesearch.py +3 -1
- scitex/scholar/url_finder/translators/individual/university_of_chicago_press_books.py +3 -1
- scitex/scholar/url_finder/translators/individual/vlex.py +3 -1
- scitex/scholar/url_finder/translators/individual/web_of_science.py +3 -1
- scitex/scholar/url_finder/translators/individual/web_of_science_nextgen.py +3 -1
- scitex/scholar/url_finder/translators/individual/wiley.py +31 -25
- scitex/scholar/url_finder/translators/individual/wilson_center_digital_archive.py +3 -1
- scitex/scholar/utils/bibtex/_parse_bibtex.py +3 -3
- scitex/scholar/utils/cleanup/_cleanup_scholar_processes.py +5 -9
- scitex/scholar/utils/text/_TextNormalizer.py +249 -176
- scitex/scholar/utils/validation/DOIValidator.py +31 -28
- scitex/scholar/utils/validation/__init__.py +0 -0
- scitex/scholar/utils/validation/validate_library_dois.py +61 -57
- scitex/scholar/zotero/__init__.py +1 -1
- scitex/security/cli.py +7 -20
- scitex/security/github.py +45 -32
- scitex/session/__init__.py +8 -9
- scitex/session/_decorator.py +49 -42
- scitex/session/_lifecycle.py +39 -39
- scitex/session/_manager.py +24 -20
- scitex/sh/__init__.py +4 -3
- scitex/sh/_execute.py +10 -7
- scitex/sh/_security.py +3 -3
- scitex/sh/_types.py +2 -3
- scitex/stats/__init__.py +174 -6
- scitex/stats/_schema.py +42 -569
- scitex/stats/auto/__init__.py +188 -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 +4 -4
- scitex/stats/correct/_correct_bonferroni.py +43 -34
- scitex/stats/correct/_correct_fdr.py +14 -40
- scitex/stats/correct/_correct_fdr_.py +39 -46
- scitex/stats/correct/_correct_holm.py +14 -32
- scitex/stats/correct/_correct_sidak.py +36 -21
- scitex/stats/descriptive/_circular.py +20 -21
- scitex/stats/descriptive/_describe.py +19 -5
- scitex/stats/descriptive/_nan.py +5 -7
- scitex/stats/descriptive/_real.py +4 -3
- scitex/stats/effect_sizes/__init__.py +10 -11
- scitex/stats/effect_sizes/_cliffs_delta.py +35 -32
- scitex/stats/effect_sizes/_cohens_d.py +30 -31
- scitex/stats/effect_sizes/_epsilon_squared.py +19 -22
- scitex/stats/effect_sizes/_eta_squared.py +23 -27
- scitex/stats/effect_sizes/_prob_superiority.py +18 -21
- scitex/stats/io/__init__.py +29 -0
- scitex/stats/io/_bundle.py +156 -0
- scitex/stats/posthoc/__init__.py +3 -3
- scitex/stats/posthoc/_dunnett.py +75 -55
- scitex/stats/posthoc/_games_howell.py +61 -43
- scitex/stats/posthoc/_tukey_hsd.py +42 -34
- scitex/stats/power/__init__.py +2 -2
- scitex/stats/power/_power.py +56 -56
- scitex/stats/tests/__init__.py +1 -1
- scitex/stats/tests/correlation/__init__.py +1 -1
- scitex/stats/tests/correlation/_test_pearson.py +28 -38
- scitex/stats/utils/__init__.py +14 -17
- scitex/stats/utils/_effect_size.py +85 -78
- scitex/stats/utils/_formatters.py +49 -43
- scitex/stats/utils/_normalizers.py +7 -14
- scitex/stats/utils/_power.py +56 -56
- scitex/str/__init__.py +1 -0
- scitex/str/_clean_path.py +3 -3
- scitex/str/_factor_out_digits.py +86 -58
- scitex/str/_format_plot_text.py +180 -111
- scitex/str/_latex.py +19 -19
- scitex/str/_latex_fallback.py +9 -10
- scitex/str/_parse.py +3 -6
- scitex/str/_print_debug.py +13 -13
- scitex/str/_printc.py +2 -0
- scitex/str/_search.py +3 -3
- scitex/template/.legacy/_clone_project.py +9 -13
- scitex/template/__init__.py +10 -2
- scitex/template/_clone_project.py +7 -2
- scitex/template/_copy.py +1 -0
- scitex/template/_customize.py +3 -6
- scitex/template/_git_strategy.py +2 -3
- scitex/template/_rename.py +1 -0
- scitex/template/clone_pip_project.py +6 -7
- scitex/template/clone_research.py +7 -10
- scitex/template/clone_singularity.py +6 -7
- scitex/template/clone_writer_directory.py +6 -7
- scitex/tex/__init__.py +4 -0
- scitex/tex/_export.py +890 -0
- scitex/tex/_preview.py +26 -11
- scitex/tex/_to_vec.py +10 -7
- scitex/torch/__init__.py +11 -1
- scitex/types/_ArrayLike.py +2 -0
- scitex/types/_is_listed_X.py +3 -3
- scitex/units.py +110 -77
- scitex/utils/_compress_hdf5.py +3 -3
- scitex/utils/_email.py +8 -4
- scitex/utils/_notify.py +14 -8
- scitex/utils/_search.py +6 -6
- scitex/utils/_verify_scitex_format.py +17 -42
- scitex/utils/_verify_scitex_format_v01.py +12 -34
- scitex/utils/template.py +4 -3
- scitex/web/__init__.py +7 -1
- scitex/web/_scraping.py +54 -38
- scitex/web/_search_pubmed.py +30 -14
- scitex/writer/.legacy/Writer_v01-refactored.py +4 -4
- scitex/writer/.legacy/_compile.py +18 -28
- scitex/writer/Writer.py +8 -21
- scitex/writer/__init__.py +11 -11
- scitex/writer/_clone_writer_project.py +2 -6
- scitex/writer/_compile/__init__.py +1 -0
- scitex/writer/_compile/_parser.py +1 -0
- scitex/writer/_compile/_runner.py +35 -38
- scitex/writer/_compile/_validator.py +1 -0
- scitex/writer/_compile/manuscript.py +1 -0
- scitex/writer/_compile/revision.py +1 -0
- scitex/writer/_compile/supplementary.py +1 -0
- scitex/writer/_compile_async.py +5 -12
- scitex/writer/_project/__init__.py +1 -0
- scitex/writer/_project/_create.py +10 -25
- scitex/writer/_project/_trees.py +4 -9
- scitex/writer/_project/_validate.py +2 -3
- scitex/writer/_validate_tree_structures.py +7 -18
- scitex/writer/dataclasses/__init__.py +8 -10
- scitex/writer/dataclasses/config/_CONSTANTS.py +2 -3
- scitex/writer/dataclasses/config/_WriterConfig.py +4 -9
- scitex/writer/dataclasses/contents/_ManuscriptContents.py +14 -25
- scitex/writer/dataclasses/contents/_RevisionContents.py +21 -16
- scitex/writer/dataclasses/contents/_SupplementaryContents.py +21 -24
- scitex/writer/dataclasses/core/_Document.py +2 -3
- scitex/writer/dataclasses/core/_DocumentSection.py +8 -23
- scitex/writer/dataclasses/results/_CompilationResult.py +2 -3
- scitex/writer/dataclasses/results/_LaTeXIssue.py +3 -6
- scitex/writer/dataclasses/results/_SaveSectionsResponse.py +20 -9
- scitex/writer/dataclasses/results/_SectionReadResponse.py +24 -10
- scitex/writer/dataclasses/tree/_ConfigTree.py +7 -4
- scitex/writer/dataclasses/tree/_ManuscriptTree.py +10 -13
- scitex/writer/dataclasses/tree/_RevisionTree.py +16 -17
- scitex/writer/dataclasses/tree/_ScriptsTree.py +10 -5
- scitex/writer/dataclasses/tree/_SharedTree.py +10 -13
- scitex/writer/dataclasses/tree/_SupplementaryTree.py +15 -14
- scitex/writer/utils/.legacy_git_retry.py +3 -8
- scitex/writer/utils/_parse_latex_logs.py +2 -3
- scitex/writer/utils/_parse_script_args.py +20 -23
- scitex/writer/utils/_watch.py +5 -5
- {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/METADATA +14 -10
- {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/RECORD +1149 -985
- scitex/db/_sqlite3/_SQLite3Mixins/_ColumnMixin_v01-indentation-issues.py +0 -583
- scitex/io/memo.md +0 -2827
- scitex/plt/_subplots/TODO.md +0 -53
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -537
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1499
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -431
- scitex/plt/_subplots/_export_as_csv_formatters.py +0 -112
- scitex/vis/__init__.py +0 -177
- scitex/vis/editor/_defaults.py +0 -244
- scitex/vis/editor/_edit.py +0 -378
- scitex/vis/editor/flask_editor/__init__.py +0 -21
- scitex/vis/editor/flask_editor/bbox.py +0 -216
- scitex/vis/editor/flask_editor/core.py +0 -152
- scitex/vis/editor/flask_editor/plotter.py +0 -130
- scitex/vis/editor/flask_editor/renderer.py +0 -184
- scitex/vis/editor/flask_editor/templates/html.py +0 -295
- scitex/vis/editor/flask_editor/templates/scripts.py +0 -614
- scitex/vis/editor/flask_editor/templates/styles.py +0 -549
- /scitex/{vis → fig}/README.md +0 -0
- /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
- {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/WHEEL +0 -0
- {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.5.0.dist-info → scitex-2.7.3.dist-info}/licenses/LICENSE +0 -0
scitex/io/_save.py
CHANGED
|
@@ -161,6 +161,7 @@ def save(
|
|
|
161
161
|
auto_crop: bool = True,
|
|
162
162
|
crop_margin_mm: float = 1.0,
|
|
163
163
|
metadata_extra: dict = None,
|
|
164
|
+
json_schema: str = "editable",
|
|
164
165
|
**kwargs,
|
|
165
166
|
) -> None:
|
|
166
167
|
"""
|
|
@@ -203,6 +204,12 @@ def save(
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
Default is None.
|
|
207
|
+
json_schema : str, optional
|
|
208
|
+
Schema type for JSON metadata output. Options:
|
|
209
|
+
- "editable": Schema v0.3.0 with element geometry for interactive editing (default)
|
|
210
|
+
- "recipe": Minimal schema with method calls + data refs
|
|
211
|
+
- "verbose": Full schema with all artist details
|
|
212
|
+
Default is "editable".
|
|
206
213
|
**kwargs
|
|
207
214
|
Additional keyword arguments to pass to the underlying save function of the specific format.
|
|
208
215
|
|
|
@@ -212,9 +219,16 @@ def save(
|
|
|
212
219
|
|
|
213
220
|
Notes
|
|
214
221
|
-----
|
|
215
|
-
Supported formats include CSV, NPY, PKL, JOBLIB, PNG, HTML, TIFF, MP4, YAML, JSON, HDF5, PTH, MAT,
|
|
222
|
+
Supported formats include CSV, NPY, PKL, JOBLIB, PNG, HTML, TIFF, MP4, YAML, JSON, HDF5, PTH, MAT, CBM,
|
|
223
|
+
and SciTeX bundles (.figz, .pltz, .statsz).
|
|
216
224
|
The function dynamically selects the appropriate saving mechanism based on the file extension.
|
|
217
225
|
|
|
226
|
+
Bundle Formats:
|
|
227
|
+
- .figz: Publication figure bundle (panels dict). Default: ZIP archive.
|
|
228
|
+
- .pltz: Plot bundle (matplotlib figure). Default: directory bundle.
|
|
229
|
+
- .statsz: Statistics bundle (comparisons list). Default: directory bundle.
|
|
230
|
+
- Use .d suffix (e.g., "Figure1.figz.d") to force directory format for .figz.
|
|
231
|
+
|
|
218
232
|
Examples
|
|
219
233
|
--------
|
|
220
234
|
>>> import scitex
|
|
@@ -296,9 +310,7 @@ def save(
|
|
|
296
310
|
elif var in frame.f_globals:
|
|
297
311
|
format_dict[var] = frame.f_globals[var]
|
|
298
312
|
else:
|
|
299
|
-
raise ValueError(
|
|
300
|
-
f"Invalid variable name in f-string: {var}"
|
|
301
|
-
)
|
|
313
|
+
raise ValueError(f"Invalid variable name in f-string: {var}")
|
|
302
314
|
|
|
303
315
|
# Use str.format() which is safe
|
|
304
316
|
specified_path = path_content.format(**format_dict)
|
|
@@ -370,7 +382,7 @@ def save(
|
|
|
370
382
|
or ("<stdin>" in script_path)
|
|
371
383
|
or env_type in ["ipython", "interactive"]
|
|
372
384
|
):
|
|
373
|
-
script_path = f
|
|
385
|
+
script_path = f"/tmp/{_os.getenv('USER')}"
|
|
374
386
|
sdir = script_path
|
|
375
387
|
else:
|
|
376
388
|
# Unknown environment, use current directory
|
|
@@ -427,6 +439,7 @@ def save(
|
|
|
427
439
|
auto_crop=auto_crop,
|
|
428
440
|
crop_margin_mm=crop_margin_mm,
|
|
429
441
|
metadata_extra=metadata_extra,
|
|
442
|
+
json_schema=json_schema,
|
|
430
443
|
**kwargs,
|
|
431
444
|
)
|
|
432
445
|
|
|
@@ -436,6 +449,9 @@ def save(
|
|
|
436
449
|
return Path(spath)
|
|
437
450
|
# return True
|
|
438
451
|
|
|
452
|
+
except AssertionError:
|
|
453
|
+
# Re-raise assertion errors - these are validation failures that should stop execution
|
|
454
|
+
raise
|
|
439
455
|
except Exception as e:
|
|
440
456
|
logger.error(
|
|
441
457
|
f"Error occurred while saving: {str(e)}\n"
|
|
@@ -471,9 +487,7 @@ def _symlink_to(spath_final, symlink_to, verbose):
|
|
|
471
487
|
|
|
472
488
|
# Ensure the symlink directory exists (only if there is a directory component)
|
|
473
489
|
symlink_dir = _os.path.dirname(symlink_to)
|
|
474
|
-
if
|
|
475
|
-
symlink_dir
|
|
476
|
-
): # Only create directory if there's a directory component
|
|
490
|
+
if symlink_dir: # Only create directory if there's a directory component
|
|
477
491
|
_os.makedirs(symlink_dir, exist_ok=True)
|
|
478
492
|
|
|
479
493
|
# Remove existing symlink or file
|
|
@@ -484,13 +498,487 @@ def _symlink_to(spath_final, symlink_to, verbose):
|
|
|
484
498
|
|
|
485
499
|
if verbose:
|
|
486
500
|
symlink_to_full = (
|
|
487
|
-
os.path.realpath(symlink_to)
|
|
488
|
-
+ "/"
|
|
489
|
-
+ os.path.basename(spath_final)
|
|
501
|
+
os.path.realpath(symlink_to) + "/" + os.path.basename(spath_final)
|
|
490
502
|
)
|
|
491
503
|
logger.success(f"Symlinked: {spath_final} -> {symlink_to_full}")
|
|
492
504
|
|
|
493
505
|
|
|
506
|
+
def _save_pltz_bundle(obj, spath, as_zip=False, data=None, layered=True, **kwargs):
|
|
507
|
+
"""Save a matplotlib figure as a .pltz bundle.
|
|
508
|
+
|
|
509
|
+
Bundle structure v2.0 (layered - default):
|
|
510
|
+
plot.pltz.d/
|
|
511
|
+
spec.json # Semantic: WHAT to plot (canonical)
|
|
512
|
+
style.json # Appearance: HOW it looks (canonical)
|
|
513
|
+
data.csv # Raw data (immutable)
|
|
514
|
+
exports/ # PNG, SVG, hitmap
|
|
515
|
+
cache/ # geometry_px.json, render_manifest.json
|
|
516
|
+
|
|
517
|
+
Bundle structure v1.0 (legacy):
|
|
518
|
+
plot.json - specification (axes, styles, theme, etc.)
|
|
519
|
+
plot.csv - raw data (immutable)
|
|
520
|
+
plot.png - raster export (required)
|
|
521
|
+
plot.svg - vector export (optional)
|
|
522
|
+
plot.pdf - publication export (optional)
|
|
523
|
+
|
|
524
|
+
Parameters
|
|
525
|
+
----------
|
|
526
|
+
obj : matplotlib.figure.Figure
|
|
527
|
+
The figure to save.
|
|
528
|
+
spath : str or Path
|
|
529
|
+
Output path (e.g., "plot.pltz.d" or "plot.pltz").
|
|
530
|
+
as_zip : bool
|
|
531
|
+
If True, save as ZIP archive.
|
|
532
|
+
data : pandas.DataFrame, optional
|
|
533
|
+
Data to embed in the bundle as plot.csv.
|
|
534
|
+
layered : bool
|
|
535
|
+
If True (default), use new layered format (spec/style/geometry).
|
|
536
|
+
If False, use legacy single JSON format.
|
|
537
|
+
**kwargs
|
|
538
|
+
Additional arguments passed to savefig.
|
|
539
|
+
"""
|
|
540
|
+
from pathlib import Path
|
|
541
|
+
import tempfile
|
|
542
|
+
import json
|
|
543
|
+
import numpy as np
|
|
544
|
+
from ._bundle import save_bundle, BundleType
|
|
545
|
+
|
|
546
|
+
p = Path(spath)
|
|
547
|
+
|
|
548
|
+
# Extract basename from path (e.g., "myplot.pltz" -> "myplot", "myplot.pltz.d" -> "myplot")
|
|
549
|
+
basename = p.stem # e.g., "myplot.pltz" or "myplot"
|
|
550
|
+
if basename.endswith('.pltz'):
|
|
551
|
+
basename = basename[:-5] # Remove .pltz suffix
|
|
552
|
+
elif basename.endswith('.d'):
|
|
553
|
+
# Handle myplot.pltz.d -> myplot.pltz -> myplot
|
|
554
|
+
basename = Path(basename).stem
|
|
555
|
+
if basename.endswith('.pltz'):
|
|
556
|
+
basename = basename[:-5]
|
|
557
|
+
|
|
558
|
+
# Extract figure from various matplotlib object types
|
|
559
|
+
import matplotlib.figure
|
|
560
|
+
fig = obj
|
|
561
|
+
if hasattr(obj, 'figure'):
|
|
562
|
+
fig = obj.figure
|
|
563
|
+
elif hasattr(obj, 'fig'):
|
|
564
|
+
fig = obj.fig
|
|
565
|
+
|
|
566
|
+
if not isinstance(fig, matplotlib.figure.Figure):
|
|
567
|
+
raise TypeError(f"Expected matplotlib Figure, got {type(obj).__name__}")
|
|
568
|
+
|
|
569
|
+
dpi = kwargs.pop('dpi', 300)
|
|
570
|
+
|
|
571
|
+
# === Always use layered format ===
|
|
572
|
+
from scitex.plt.io import save_layered_pltz_bundle
|
|
573
|
+
import shutil
|
|
574
|
+
import tempfile
|
|
575
|
+
|
|
576
|
+
# Determine bundle directory path
|
|
577
|
+
if as_zip:
|
|
578
|
+
# For ZIP: save to temp dir, then compress
|
|
579
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
580
|
+
bundle_dir = temp_dir / f"{basename}.pltz.d"
|
|
581
|
+
zip_path = p if not str(p).endswith('.d') else Path(str(p)[:-2])
|
|
582
|
+
else:
|
|
583
|
+
# For directory: save directly
|
|
584
|
+
bundle_dir = p if str(p).endswith('.d') else Path(str(p) + '.d')
|
|
585
|
+
|
|
586
|
+
# Get CSV data from figure if not provided
|
|
587
|
+
csv_df = data
|
|
588
|
+
if csv_df is None:
|
|
589
|
+
csv_source = _get_figure_with_data(obj)
|
|
590
|
+
if csv_source is not None and hasattr(csv_source, 'export_as_csv'):
|
|
591
|
+
try:
|
|
592
|
+
csv_df = csv_source.export_as_csv()
|
|
593
|
+
except Exception:
|
|
594
|
+
pass
|
|
595
|
+
|
|
596
|
+
save_layered_pltz_bundle(
|
|
597
|
+
fig=fig,
|
|
598
|
+
bundle_dir=bundle_dir,
|
|
599
|
+
basename=basename,
|
|
600
|
+
dpi=dpi,
|
|
601
|
+
csv_df=csv_df,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
# Compress to ZIP if requested
|
|
605
|
+
if as_zip:
|
|
606
|
+
from ._bundle import pack_bundle
|
|
607
|
+
pack_bundle(bundle_dir, zip_path)
|
|
608
|
+
shutil.rmtree(temp_dir) # Clean up temp directory
|
|
609
|
+
|
|
610
|
+
return # Done with layered format
|
|
611
|
+
|
|
612
|
+
# === Legacy format below (DEPRECATED - kept for reference) ===
|
|
613
|
+
|
|
614
|
+
# Calculate size info
|
|
615
|
+
fig_width_inch, fig_height_inch = fig.get_size_inches()
|
|
616
|
+
fig_dpi = fig.get_dpi()
|
|
617
|
+
|
|
618
|
+
# Build spec according to contract (using basename for file references)
|
|
619
|
+
spec = {
|
|
620
|
+
'schema': {'name': 'scitex.plt.plot', 'version': '1.0.0'},
|
|
621
|
+
'backend': 'mpl',
|
|
622
|
+
'data': {
|
|
623
|
+
'source': f'{basename}.csv',
|
|
624
|
+
'path': f'{basename}.csv',
|
|
625
|
+
'hash': None, # Will be computed after data extraction
|
|
626
|
+
'columns': [], # Will be populated after data extraction
|
|
627
|
+
},
|
|
628
|
+
'size': {
|
|
629
|
+
'width_inch': round(fig_width_inch, 2),
|
|
630
|
+
'height_inch': round(fig_height_inch, 2),
|
|
631
|
+
'width_mm': round(fig_width_inch * 25.4, 2),
|
|
632
|
+
'height_mm': round(fig_height_inch * 25.4, 2),
|
|
633
|
+
'width_px': int(fig_width_inch * dpi),
|
|
634
|
+
'height_px': int(fig_height_inch * dpi),
|
|
635
|
+
'dpi': dpi,
|
|
636
|
+
'crop_margin_mm': 1.0,
|
|
637
|
+
},
|
|
638
|
+
'axes': [],
|
|
639
|
+
'theme': {
|
|
640
|
+
'mode': 'light',
|
|
641
|
+
'colors': {
|
|
642
|
+
'background': 'transparent',
|
|
643
|
+
'axes_bg': 'white',
|
|
644
|
+
'text': 'black',
|
|
645
|
+
'spine': 'black',
|
|
646
|
+
'tick': 'black',
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
# Extract data from plot lines if no data provided
|
|
652
|
+
extracted_data = {}
|
|
653
|
+
|
|
654
|
+
# Extract axes metadata
|
|
655
|
+
for i, ax in enumerate(fig.axes):
|
|
656
|
+
# Get axes bounding box in figure coordinates (0-1)
|
|
657
|
+
bbox = ax.get_position()
|
|
658
|
+
|
|
659
|
+
ax_info = {
|
|
660
|
+
'xlabel': ax.get_xlabel() or None,
|
|
661
|
+
'ylabel': ax.get_ylabel() or None,
|
|
662
|
+
'title': ax.get_title() or None,
|
|
663
|
+
'xlim': [round(v, 2) for v in ax.get_xlim()],
|
|
664
|
+
'ylim': [round(v, 2) for v in ax.get_ylim()],
|
|
665
|
+
'plot_type': 'line', # Default, could be detected
|
|
666
|
+
# Bounding box in normalized figure coordinates (0-1)
|
|
667
|
+
'bbox': {
|
|
668
|
+
'x0': round(bbox.x0, 4),
|
|
669
|
+
'y0': round(bbox.y0, 4),
|
|
670
|
+
'x1': round(bbox.x1, 4),
|
|
671
|
+
'y1': round(bbox.y1, 4),
|
|
672
|
+
'width': round(bbox.width, 4),
|
|
673
|
+
'height': round(bbox.height, 4),
|
|
674
|
+
},
|
|
675
|
+
# Bounding box in mm
|
|
676
|
+
'bbox_mm': {
|
|
677
|
+
'x0': round(bbox.x0 * fig_width_inch * 25.4, 2),
|
|
678
|
+
'y0': round(bbox.y0 * fig_height_inch * 25.4, 2),
|
|
679
|
+
'x1': round(bbox.x1 * fig_width_inch * 25.4, 2),
|
|
680
|
+
'y1': round(bbox.y1 * fig_height_inch * 25.4, 2),
|
|
681
|
+
'width': round(bbox.width * fig_width_inch * 25.4, 2),
|
|
682
|
+
'height': round(bbox.height * fig_height_inch * 25.4, 2),
|
|
683
|
+
},
|
|
684
|
+
# Bounding box in pixels
|
|
685
|
+
'bbox_px': {
|
|
686
|
+
'x0': int(bbox.x0 * fig_width_inch * dpi),
|
|
687
|
+
'y0': int(bbox.y0 * fig_height_inch * dpi),
|
|
688
|
+
'x1': int(bbox.x1 * fig_width_inch * dpi),
|
|
689
|
+
'y1': int(bbox.y1 * fig_height_inch * dpi),
|
|
690
|
+
'width': int(bbox.width * fig_width_inch * dpi),
|
|
691
|
+
'height': int(bbox.height * fig_height_inch * dpi),
|
|
692
|
+
},
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
# SciTeX-specific axis dimensions
|
|
696
|
+
if hasattr(ax, '_scitex_axes_width_mm'):
|
|
697
|
+
ax_info['axes_width_mm'] = ax._scitex_axes_width_mm
|
|
698
|
+
else:
|
|
699
|
+
ax_info['axes_width_mm'] = round(bbox.width * fig_width_inch * 25.4, 1)
|
|
700
|
+
|
|
701
|
+
if hasattr(ax, '_scitex_axes_height_mm'):
|
|
702
|
+
ax_info['axes_height_mm'] = ax._scitex_axes_height_mm
|
|
703
|
+
else:
|
|
704
|
+
ax_info['axes_height_mm'] = round(bbox.height * fig_height_inch * 25.4, 1)
|
|
705
|
+
|
|
706
|
+
# Extract line data for CSV and build lines array
|
|
707
|
+
lines_info = []
|
|
708
|
+
for j, line in enumerate(ax.get_lines()):
|
|
709
|
+
label = line.get_label()
|
|
710
|
+
if label is None or label.startswith('_'):
|
|
711
|
+
label = f'series_{j}'
|
|
712
|
+
xdata, ydata = line.get_data()
|
|
713
|
+
if len(xdata) > 0:
|
|
714
|
+
col_x = f'{label}_x' if i == 0 else f'ax{i}_{label}_x'
|
|
715
|
+
col_y = f'{label}_y' if i == 0 else f'ax{i}_{label}_y'
|
|
716
|
+
extracted_data[col_x] = np.array(xdata)
|
|
717
|
+
extracted_data[col_y] = np.array(ydata)
|
|
718
|
+
|
|
719
|
+
# Get line color (convert RGBA to hex)
|
|
720
|
+
color = line.get_color()
|
|
721
|
+
if isinstance(color, (list, tuple)):
|
|
722
|
+
import matplotlib.colors as mcolors
|
|
723
|
+
color = mcolors.to_hex(color)
|
|
724
|
+
|
|
725
|
+
lines_info.append({
|
|
726
|
+
'label': label,
|
|
727
|
+
'x_col': col_x,
|
|
728
|
+
'y_col': col_y,
|
|
729
|
+
'color': color,
|
|
730
|
+
'linewidth': line.get_linewidth(),
|
|
731
|
+
})
|
|
732
|
+
|
|
733
|
+
if lines_info:
|
|
734
|
+
ax_info['lines'] = lines_info
|
|
735
|
+
|
|
736
|
+
spec['axes'].append(ax_info)
|
|
737
|
+
|
|
738
|
+
# Handle theme from figure
|
|
739
|
+
if hasattr(fig, '_scitex_theme'):
|
|
740
|
+
theme_mode = fig._scitex_theme
|
|
741
|
+
spec['theme']['mode'] = theme_mode
|
|
742
|
+
# Update colors based on theme mode
|
|
743
|
+
if theme_mode == 'dark':
|
|
744
|
+
spec['theme']['colors'] = {
|
|
745
|
+
'background': 'transparent',
|
|
746
|
+
'axes_bg': 'transparent',
|
|
747
|
+
'text': '#e8e8e8',
|
|
748
|
+
'spine': '#e8e8e8',
|
|
749
|
+
'tick': '#e8e8e8',
|
|
750
|
+
}
|
|
751
|
+
# Re-apply theme colors to ensure legends and other elements get the correct colors
|
|
752
|
+
from scitex.plt.utils._figure_mm import _apply_theme_colors
|
|
753
|
+
for ax in fig.axes:
|
|
754
|
+
_apply_theme_colors(ax, theme='dark')
|
|
755
|
+
|
|
756
|
+
# Build bundle data (include basename for file naming)
|
|
757
|
+
bundle_data = {'spec': spec, 'basename': basename}
|
|
758
|
+
|
|
759
|
+
# Use provided data or extracted data for CSV
|
|
760
|
+
# Priority: 1) explicit data param, 2) export_as_csv method, 3) line extraction fallback
|
|
761
|
+
csv_df = None
|
|
762
|
+
if data is not None:
|
|
763
|
+
csv_df = data
|
|
764
|
+
bundle_data['data'] = data
|
|
765
|
+
else:
|
|
766
|
+
# Try to use export_as_csv from SciTeX wrapped objects (handles all plot types)
|
|
767
|
+
csv_source = _get_figure_with_data(obj)
|
|
768
|
+
if csv_source is not None and hasattr(csv_source, 'export_as_csv'):
|
|
769
|
+
try:
|
|
770
|
+
csv_df = csv_source.export_as_csv()
|
|
771
|
+
if csv_df is not None and not csv_df.empty:
|
|
772
|
+
bundle_data['data'] = csv_df
|
|
773
|
+
logger.debug(f"CSV data extracted via export_as_csv: {len(csv_df)} rows, {len(csv_df.columns)} cols")
|
|
774
|
+
except Exception as e:
|
|
775
|
+
logger.debug(f"export_as_csv failed: {e}")
|
|
776
|
+
csv_df = None
|
|
777
|
+
|
|
778
|
+
# Fallback to line extraction if export_as_csv didn't work
|
|
779
|
+
if csv_df is None and extracted_data:
|
|
780
|
+
try:
|
|
781
|
+
import pandas as pd
|
|
782
|
+
# Pad arrays to same length
|
|
783
|
+
max_len = max(len(v) for v in extracted_data.values())
|
|
784
|
+
padded = {}
|
|
785
|
+
for k, v in extracted_data.items():
|
|
786
|
+
if len(v) < max_len:
|
|
787
|
+
padded[k] = np.pad(v, (0, max_len - len(v)), constant_values=np.nan)
|
|
788
|
+
else:
|
|
789
|
+
padded[k] = v
|
|
790
|
+
csv_df = pd.DataFrame(padded)
|
|
791
|
+
bundle_data['data'] = csv_df
|
|
792
|
+
logger.debug(f"CSV data extracted via line fallback: {len(csv_df)} rows")
|
|
793
|
+
except ImportError:
|
|
794
|
+
pass
|
|
795
|
+
|
|
796
|
+
# Compute hash and columns for data section
|
|
797
|
+
if csv_df is not None:
|
|
798
|
+
import hashlib
|
|
799
|
+
# Get CSV string for hash computation
|
|
800
|
+
csv_str = csv_df.to_csv(index=False)
|
|
801
|
+
csv_hash = hashlib.sha256(csv_str.encode()).hexdigest()
|
|
802
|
+
spec['data']['hash'] = f'sha256:{csv_hash[:16]}'
|
|
803
|
+
spec['data']['columns'] = list(csv_df.columns)
|
|
804
|
+
|
|
805
|
+
# Save figure to multiple formats
|
|
806
|
+
import warnings
|
|
807
|
+
from PIL import Image as PILImage
|
|
808
|
+
from scitex.plt.utils._hitmap import (
|
|
809
|
+
apply_hitmap_colors, restore_original_colors, extract_path_data,
|
|
810
|
+
extract_selectable_regions, HITMAP_BACKGROUND_COLOR, HITMAP_AXES_COLOR
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
crop_box = None
|
|
814
|
+
color_map = {}
|
|
815
|
+
|
|
816
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
817
|
+
tmp_path = Path(tmp_dir)
|
|
818
|
+
|
|
819
|
+
# Suppress tight_layout warnings for SciTeX figures with custom axes
|
|
820
|
+
with warnings.catch_warnings():
|
|
821
|
+
warnings.filterwarnings('ignore', message='.*tight_layout.*')
|
|
822
|
+
|
|
823
|
+
# Always use transparent background for SciTeX figures (both light and dark themes)
|
|
824
|
+
use_transparent = True
|
|
825
|
+
|
|
826
|
+
# Save PNG (raster) - required
|
|
827
|
+
png_path = tmp_path / "plot.png"
|
|
828
|
+
fig.savefig(png_path, dpi=dpi, bbox_inches='tight', format='png', transparent=use_transparent)
|
|
829
|
+
|
|
830
|
+
# Save SVG (vector) - optional
|
|
831
|
+
svg_path = tmp_path / "plot.svg"
|
|
832
|
+
fig.savefig(svg_path, bbox_inches='tight', format='svg')
|
|
833
|
+
|
|
834
|
+
# Save PDF (vector) - optional
|
|
835
|
+
pdf_path = tmp_path / "plot.pdf"
|
|
836
|
+
fig.savefig(pdf_path, bbox_inches='tight', format='pdf')
|
|
837
|
+
|
|
838
|
+
# Now generate hitmap by applying ID colors to data elements ONLY
|
|
839
|
+
# Keep axes/spines/labels with original colors to preserve bbox_inches='tight' bounds
|
|
840
|
+
# Also detects logical groups (histogram, bar_series, etc.)
|
|
841
|
+
original_props, color_map, groups = apply_hitmap_colors(fig)
|
|
842
|
+
|
|
843
|
+
# Store original background colors and set hitmap colors
|
|
844
|
+
original_fig_facecolor = fig.patch.get_facecolor()
|
|
845
|
+
original_ax_facecolors = []
|
|
846
|
+
original_ax_props = []
|
|
847
|
+
for ax in fig.axes:
|
|
848
|
+
original_ax_facecolors.append(ax.get_facecolor())
|
|
849
|
+
# Store axis element colors for restoration
|
|
850
|
+
ax_props = {
|
|
851
|
+
'ax': ax,
|
|
852
|
+
'spine_colors': {k: v.get_edgecolor() for k, v in ax.spines.items()},
|
|
853
|
+
'tick_colors': ax.tick_params, # Will restore later
|
|
854
|
+
'xlabel_color': ax.xaxis.label.get_color(),
|
|
855
|
+
'ylabel_color': ax.yaxis.label.get_color(),
|
|
856
|
+
'title_color': ax.title.get_color(),
|
|
857
|
+
}
|
|
858
|
+
original_ax_props.append(ax_props)
|
|
859
|
+
# Set hitmap colors for non-data elements
|
|
860
|
+
ax.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
861
|
+
for spine in ax.spines.values():
|
|
862
|
+
spine.set_color(HITMAP_AXES_COLOR)
|
|
863
|
+
ax.tick_params(colors=HITMAP_AXES_COLOR, labelcolor=HITMAP_AXES_COLOR)
|
|
864
|
+
ax.xaxis.label.set_color(HITMAP_AXES_COLOR)
|
|
865
|
+
ax.yaxis.label.set_color(HITMAP_AXES_COLOR)
|
|
866
|
+
ax.title.set_color(HITMAP_AXES_COLOR)
|
|
867
|
+
|
|
868
|
+
fig.patch.set_facecolor(HITMAP_BACKGROUND_COLOR)
|
|
869
|
+
|
|
870
|
+
# Save hitmap PNG with same bbox_inches='tight'
|
|
871
|
+
hitmap_path = tmp_path / "plot_hitmap.png"
|
|
872
|
+
fig.savefig(hitmap_path, dpi=dpi, bbox_inches='tight', format='png', facecolor=HITMAP_BACKGROUND_COLOR)
|
|
873
|
+
|
|
874
|
+
# Optimize hitmap PNG size using zlib compression
|
|
875
|
+
try:
|
|
876
|
+
hitmap_img = PILImage.open(hitmap_path).convert('RGB')
|
|
877
|
+
hitmap_img.save(hitmap_path, format='PNG', optimize=True, compress_level=9)
|
|
878
|
+
except Exception:
|
|
879
|
+
pass # Keep original if optimization fails
|
|
880
|
+
|
|
881
|
+
# Save hitmap SVG with same bbox_inches='tight'
|
|
882
|
+
hitmap_svg_path = tmp_path / "plot_hitmap.svg"
|
|
883
|
+
fig.savefig(hitmap_svg_path, bbox_inches='tight', format='svg')
|
|
884
|
+
|
|
885
|
+
# Restore original colors (data elements)
|
|
886
|
+
restore_original_colors(original_props)
|
|
887
|
+
|
|
888
|
+
# Restore original figure and axes colors
|
|
889
|
+
fig.patch.set_facecolor(original_fig_facecolor)
|
|
890
|
+
for i, ax in enumerate(fig.axes):
|
|
891
|
+
ax.set_facecolor(original_ax_facecolors[i])
|
|
892
|
+
if i < len(original_ax_props):
|
|
893
|
+
props = original_ax_props[i]
|
|
894
|
+
for spine_name, color in props['spine_colors'].items():
|
|
895
|
+
ax.spines[spine_name].set_edgecolor(color)
|
|
896
|
+
ax.xaxis.label.set_color(props['xlabel_color'])
|
|
897
|
+
ax.yaxis.label.set_color(props['ylabel_color'])
|
|
898
|
+
ax.title.set_color(props['title_color'])
|
|
899
|
+
|
|
900
|
+
# Now apply auto-crop to BOTH PNG and hitmap with same parameters
|
|
901
|
+
try:
|
|
902
|
+
from scitex.plt.utils._crop import crop
|
|
903
|
+
|
|
904
|
+
# Crop PNG and get crop coordinates
|
|
905
|
+
_, crop_offset = crop(
|
|
906
|
+
str(png_path),
|
|
907
|
+
output_path=str(png_path),
|
|
908
|
+
overwrite=True,
|
|
909
|
+
margin=12, # ~1mm at 300 DPI
|
|
910
|
+
verbose=False,
|
|
911
|
+
return_offset=True,
|
|
912
|
+
)
|
|
913
|
+
crop_box = (crop_offset['left'], crop_offset['upper'],
|
|
914
|
+
crop_offset['right'], crop_offset['lower'])
|
|
915
|
+
|
|
916
|
+
# Apply SAME crop to hitmap PNG
|
|
917
|
+
crop(
|
|
918
|
+
str(hitmap_path),
|
|
919
|
+
output_path=str(hitmap_path),
|
|
920
|
+
overwrite=True,
|
|
921
|
+
crop_box=crop_box,
|
|
922
|
+
verbose=False,
|
|
923
|
+
)
|
|
924
|
+
except Exception as e:
|
|
925
|
+
crop_box = None
|
|
926
|
+
logger.debug(f"Crop failed: {e}")
|
|
927
|
+
|
|
928
|
+
# Validate sizes match
|
|
929
|
+
with PILImage.open(png_path) as png_img, PILImage.open(hitmap_path) as hm_img:
|
|
930
|
+
if png_img.size != hm_img.size:
|
|
931
|
+
logger.warning(f"Size mismatch: PNG={png_img.size}, Hitmap={hm_img.size}")
|
|
932
|
+
|
|
933
|
+
with open(png_path, 'rb') as f:
|
|
934
|
+
bundle_data['png'] = f.read()
|
|
935
|
+
|
|
936
|
+
with open(hitmap_path, 'rb') as f:
|
|
937
|
+
bundle_data['hitmap_png'] = f.read()
|
|
938
|
+
|
|
939
|
+
with open(svg_path, 'rb') as f:
|
|
940
|
+
bundle_data['svg'] = f.read()
|
|
941
|
+
|
|
942
|
+
with open(hitmap_svg_path, 'rb') as f:
|
|
943
|
+
bundle_data['hitmap_svg'] = f.read()
|
|
944
|
+
|
|
945
|
+
with open(pdf_path, 'rb') as f:
|
|
946
|
+
bundle_data['pdf'] = f.read()
|
|
947
|
+
|
|
948
|
+
# Add hit_regions to spec
|
|
949
|
+
try:
|
|
950
|
+
path_data = extract_path_data(fig)
|
|
951
|
+
|
|
952
|
+
spec['hit_regions'] = {
|
|
953
|
+
'strategy': 'hybrid',
|
|
954
|
+
'hit_map': f'{basename}_hitmap.png',
|
|
955
|
+
'hit_map_svg': f'{basename}_hitmap.svg',
|
|
956
|
+
'color_map': {str(k): v for k, v in color_map.items()},
|
|
957
|
+
'groups': groups, # Logical groups (histogram, bar_series, etc.)
|
|
958
|
+
'path_data': path_data,
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if crop_box is not None:
|
|
962
|
+
spec['hit_regions']['crop_box'] = {
|
|
963
|
+
'left': int(crop_box[0]),
|
|
964
|
+
'upper': int(crop_box[1]),
|
|
965
|
+
'right': int(crop_box[2]),
|
|
966
|
+
'lower': int(crop_box[3]),
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
# Extract selectable regions (bounding boxes for axis/annotation elements)
|
|
970
|
+
# This complements hitmap color-based selection with bbox-based selection
|
|
971
|
+
selectable_regions = extract_selectable_regions(fig)
|
|
972
|
+
if selectable_regions and selectable_regions.get('axes'):
|
|
973
|
+
spec['selectable_regions'] = selectable_regions
|
|
974
|
+
|
|
975
|
+
except Exception as e:
|
|
976
|
+
logger.debug(f"Hit regions spec failed: {e}")
|
|
977
|
+
|
|
978
|
+
# Save the bundle
|
|
979
|
+
save_bundle(bundle_data, p, bundle_type=BundleType.PLTZ, as_zip=as_zip)
|
|
980
|
+
|
|
981
|
+
|
|
494
982
|
def _save(
|
|
495
983
|
obj,
|
|
496
984
|
spath,
|
|
@@ -502,6 +990,7 @@ def _save(
|
|
|
502
990
|
auto_crop=False,
|
|
503
991
|
crop_margin_mm=1.0,
|
|
504
992
|
metadata_extra=None,
|
|
993
|
+
json_schema="editable",
|
|
505
994
|
**kwargs,
|
|
506
995
|
):
|
|
507
996
|
# Don't use object's own save method - use consistent handlers
|
|
@@ -516,6 +1005,50 @@ def _save(
|
|
|
516
1005
|
save_canvas(obj, spath, **kwargs)
|
|
517
1006
|
return
|
|
518
1007
|
|
|
1008
|
+
# Handle bundle formats (.figz, .pltz, .statsz and their .d variants)
|
|
1009
|
+
# These use special naming: file.figz (ZIP) or file.figz.d (directory)
|
|
1010
|
+
# Note: .figz defaults to ZIP (as_zip=True), .pltz/.statsz default to directory
|
|
1011
|
+
bundle_extensions = (".figz", ".pltz", ".statsz")
|
|
1012
|
+
for bext in bundle_extensions:
|
|
1013
|
+
if spath.endswith(bext) or spath.endswith(f"{bext}.d"):
|
|
1014
|
+
# Remove as_zip from kwargs if present to avoid duplicate
|
|
1015
|
+
bundle_kwargs = {k: v for k, v in kwargs.items() if k != 'as_zip'}
|
|
1016
|
+
as_zip = kwargs.get('as_zip', not spath.endswith(".d"))
|
|
1017
|
+
if bext == ".figz":
|
|
1018
|
+
import scitex.fig as sfig
|
|
1019
|
+
# figz defaults to ZIP, so always pass as_zip explicitly
|
|
1020
|
+
sfig.save_figz(obj, spath, as_zip=as_zip, **bundle_kwargs)
|
|
1021
|
+
elif bext == ".pltz":
|
|
1022
|
+
_save_pltz_bundle(obj, spath, as_zip=as_zip, **bundle_kwargs)
|
|
1023
|
+
elif bext == ".statsz":
|
|
1024
|
+
import scitex.stats as sstats
|
|
1025
|
+
sstats.save_statsz(obj, spath, as_zip=as_zip, **bundle_kwargs)
|
|
1026
|
+
|
|
1027
|
+
# Log "Saved to:" for bundle formats (consistent with other formats)
|
|
1028
|
+
# For bundles, determine the actual saved path (zip or directory)
|
|
1029
|
+
bundle_path = spath if as_zip else f"{spath}.d" if not spath.endswith(".d") else spath
|
|
1030
|
+
|
|
1031
|
+
if verbose and _os.path.exists(bundle_path):
|
|
1032
|
+
file_size = getsize(bundle_path)
|
|
1033
|
+
file_size = readable_bytes(file_size)
|
|
1034
|
+
try:
|
|
1035
|
+
rel_path = _os.path.relpath(bundle_path, _os.getcwd())
|
|
1036
|
+
except ValueError:
|
|
1037
|
+
rel_path = bundle_path
|
|
1038
|
+
logger.success(f"Saved to: ./{rel_path} ({file_size})")
|
|
1039
|
+
|
|
1040
|
+
# Handle symlinks for bundle formats (consistent with other formats)
|
|
1041
|
+
if symlink_from_cwd and _os.path.exists(bundle_path):
|
|
1042
|
+
# Create symlink from cwd to bundle path
|
|
1043
|
+
bundle_basename = _os.path.basename(bundle_path)
|
|
1044
|
+
bundle_cwd = _os.path.join(_os.getcwd(), bundle_basename)
|
|
1045
|
+
_symlink(bundle_path, bundle_cwd, symlink_from_cwd, verbose)
|
|
1046
|
+
|
|
1047
|
+
if symlink_to and _os.path.exists(bundle_path):
|
|
1048
|
+
_symlink_to(bundle_path, symlink_to, verbose)
|
|
1049
|
+
|
|
1050
|
+
return
|
|
1051
|
+
|
|
519
1052
|
# Try dispatch dictionary first for O(1) lookup
|
|
520
1053
|
if ext in _FILE_HANDLERS:
|
|
521
1054
|
# Check if handler needs special parameters
|
|
@@ -540,6 +1073,7 @@ def _save(
|
|
|
540
1073
|
auto_crop=auto_crop,
|
|
541
1074
|
crop_margin_mm=crop_margin_mm,
|
|
542
1075
|
metadata_extra=metadata_extra,
|
|
1076
|
+
json_schema=json_schema,
|
|
543
1077
|
**kwargs,
|
|
544
1078
|
)
|
|
545
1079
|
elif ext in [".hdf5", ".h5", ".zarr"]:
|
|
@@ -554,7 +1088,7 @@ def _save(
|
|
|
554
1088
|
elif spath.endswith(".pkl.gz"):
|
|
555
1089
|
save_pickle_compressed(obj, spath, **kwargs)
|
|
556
1090
|
else:
|
|
557
|
-
|
|
1091
|
+
logger.warning(f"Unsupported file format. {spath} was not saved.")
|
|
558
1092
|
|
|
559
1093
|
if verbose:
|
|
560
1094
|
if _os.path.exists(spath):
|
|
@@ -569,9 +1103,7 @@ def _save(
|
|
|
569
1103
|
logger.success(f"Saved to: ./{rel_path} ({file_size})")
|
|
570
1104
|
|
|
571
1105
|
|
|
572
|
-
def _save_separate_legends(
|
|
573
|
-
obj, spath, symlink_from_cwd=False, dry_run=False, **kwargs
|
|
574
|
-
):
|
|
1106
|
+
def _save_separate_legends(obj, spath, symlink_from_cwd=False, dry_run=False, **kwargs):
|
|
575
1107
|
"""Save separate legend files if ax.legend('separate') was used."""
|
|
576
1108
|
if dry_run:
|
|
577
1109
|
return
|
|
@@ -653,6 +1185,7 @@ def _handle_image_with_csv(
|
|
|
653
1185
|
auto_crop=True,
|
|
654
1186
|
crop_margin_mm=1.0,
|
|
655
1187
|
metadata_extra=None,
|
|
1188
|
+
json_schema="editable",
|
|
656
1189
|
**kwargs,
|
|
657
1190
|
):
|
|
658
1191
|
"""Handle image file saving with optional CSV export and auto-cropping."""
|
|
@@ -661,7 +1194,7 @@ def _handle_image_with_csv(
|
|
|
661
1194
|
|
|
662
1195
|
# Auto-collect metadata from scitex figures if not explicitly provided
|
|
663
1196
|
collected_metadata = None
|
|
664
|
-
if
|
|
1197
|
+
if "metadata" not in kwargs or kwargs["metadata"] is None:
|
|
665
1198
|
try:
|
|
666
1199
|
# Check if this is a matplotlib figure or scitex wrapper
|
|
667
1200
|
import matplotlib.figure
|
|
@@ -669,93 +1202,155 @@ def _handle_image_with_csv(
|
|
|
669
1202
|
fig_mpl = None
|
|
670
1203
|
if isinstance(obj, matplotlib.figure.Figure):
|
|
671
1204
|
fig_mpl = obj
|
|
672
|
-
elif hasattr(obj,
|
|
1205
|
+
elif hasattr(obj, "_fig_mpl"): # FigWrapper
|
|
673
1206
|
fig_mpl = obj._fig_mpl
|
|
674
|
-
elif hasattr(obj,
|
|
1207
|
+
elif hasattr(obj, "figure") and isinstance(
|
|
1208
|
+
obj.figure, matplotlib.figure.Figure
|
|
1209
|
+
):
|
|
675
1210
|
fig_mpl = obj.figure
|
|
676
1211
|
|
|
677
1212
|
# If we have a figure, try to collect metadata
|
|
678
1213
|
if fig_mpl is not None:
|
|
679
|
-
# Get
|
|
1214
|
+
# Get axes from scitex wrapper if available (for multi-axes support)
|
|
1215
|
+
# Priority: FigWrapper.axes (AxesWrapper) > mpl axes with _scitex_wrapper > mpl axes
|
|
680
1216
|
ax = None
|
|
681
|
-
|
|
682
|
-
|
|
1217
|
+
|
|
1218
|
+
# First try to get AxesWrapper from FigWrapper (obj)
|
|
1219
|
+
if hasattr(obj, "axes"):
|
|
1220
|
+
# obj is FigWrapper, get its axes (could be AxisWrapper or AxesWrapper)
|
|
1221
|
+
ax = obj.axes
|
|
1222
|
+
elif hasattr(fig_mpl, "axes") and len(fig_mpl.axes) > 0:
|
|
1223
|
+
mpl_ax = fig_mpl.axes[0]
|
|
1224
|
+
# Try to get scitex wrapper which has history for recipe schema
|
|
1225
|
+
if hasattr(mpl_ax, '_scitex_wrapper'):
|
|
1226
|
+
ax = mpl_ax._scitex_wrapper
|
|
1227
|
+
else:
|
|
1228
|
+
ax = mpl_ax
|
|
683
1229
|
|
|
684
1230
|
# Collect metadata using scitex's metadata collector
|
|
685
1231
|
try:
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1232
|
+
if json_schema == "editable":
|
|
1233
|
+
from scitex.plt.utils.metadata import export_editable_figure
|
|
1234
|
+
auto_metadata = export_editable_figure(fig_mpl)
|
|
1235
|
+
elif json_schema == "recipe":
|
|
1236
|
+
from scitex.plt.utils import collect_recipe_metadata
|
|
1237
|
+
auto_metadata = collect_recipe_metadata(
|
|
1238
|
+
fig_mpl, ax,
|
|
1239
|
+
auto_crop=auto_crop,
|
|
1240
|
+
crop_margin_mm=crop_margin_mm,
|
|
1241
|
+
)
|
|
1242
|
+
else:
|
|
1243
|
+
from scitex.plt.utils import collect_figure_metadata
|
|
1244
|
+
auto_metadata = collect_figure_metadata(fig_mpl, ax)
|
|
692
1245
|
|
|
693
1246
|
if auto_metadata:
|
|
694
|
-
kwargs[
|
|
1247
|
+
kwargs["metadata"] = auto_metadata
|
|
695
1248
|
collected_metadata = auto_metadata # Save for JSON export
|
|
696
1249
|
if verbose:
|
|
697
|
-
|
|
1250
|
+
schema_names = {"editable": "editable v0.3", "recipe": "recipe", "verbose": "verbose"}
|
|
1251
|
+
schema_name = schema_names.get(json_schema, json_schema)
|
|
1252
|
+
logger.info(f" • Auto-collected metadata ({schema_name} schema)")
|
|
698
1253
|
except ImportError:
|
|
699
1254
|
pass # collect_figure_metadata not available
|
|
700
1255
|
except Exception as e:
|
|
701
1256
|
if verbose:
|
|
702
|
-
|
|
703
|
-
warnings.warn(f"Could not auto-collect metadata: {e}")
|
|
1257
|
+
logger.warning(f"Could not auto-collect metadata: {e}")
|
|
704
1258
|
except Exception:
|
|
705
1259
|
pass # Silently continue if auto-collection fails
|
|
706
1260
|
else:
|
|
707
1261
|
# Use explicitly provided metadata
|
|
708
|
-
collected_metadata = kwargs.get(
|
|
1262
|
+
collected_metadata = kwargs.get("metadata")
|
|
709
1263
|
|
|
710
1264
|
# Merge metadata_extra with collected_metadata
|
|
711
1265
|
if metadata_extra is not None and collected_metadata is not None:
|
|
712
1266
|
# Deep merge: metadata_extra takes precedence
|
|
713
1267
|
import copy
|
|
1268
|
+
|
|
714
1269
|
collected_metadata = copy.deepcopy(collected_metadata)
|
|
715
1270
|
|
|
716
1271
|
# If metadata_extra has plot_type and it doesn't exist in collected, add it
|
|
717
|
-
if
|
|
718
|
-
collected_metadata[
|
|
1272
|
+
if "plot_type" in metadata_extra:
|
|
1273
|
+
collected_metadata["plot_type"] = metadata_extra["plot_type"]
|
|
719
1274
|
|
|
720
1275
|
# Merge style information
|
|
721
|
-
if
|
|
722
|
-
collected_metadata[
|
|
1276
|
+
if "style" in metadata_extra:
|
|
1277
|
+
collected_metadata["style"] = metadata_extra["style"]
|
|
723
1278
|
|
|
724
1279
|
# Merge any other fields from metadata_extra
|
|
725
1280
|
for key, value in metadata_extra.items():
|
|
726
|
-
if key not in [
|
|
1281
|
+
if key not in ["plot_type", "style"]:
|
|
727
1282
|
collected_metadata[key] = value
|
|
728
1283
|
|
|
729
1284
|
# Update kwargs metadata for image saving
|
|
730
|
-
kwargs[
|
|
1285
|
+
kwargs["metadata"] = collected_metadata
|
|
731
1286
|
|
|
732
1287
|
save_image(obj, spath, verbose=verbose, **kwargs)
|
|
733
1288
|
|
|
734
1289
|
# Auto-crop if requested (only for raster formats)
|
|
1290
|
+
crop_offset = None
|
|
735
1291
|
if auto_crop and not dry_run:
|
|
736
1292
|
# Get file extension
|
|
737
1293
|
ext = spath.lower()
|
|
738
1294
|
|
|
739
1295
|
# Only crop raster formats (PNG, JPEG, TIFF)
|
|
740
1296
|
# Skip vector formats (PDF, SVG) as they don't benefit from cropping
|
|
741
|
-
if ext.endswith((
|
|
1297
|
+
if ext.endswith((".png", ".jpg", ".jpeg", ".tiff", ".tif")):
|
|
742
1298
|
try:
|
|
743
1299
|
from scitex.plt.utils._crop import crop
|
|
744
1300
|
|
|
745
1301
|
# Convert mm to pixels (assuming 300 DPI)
|
|
746
1302
|
# 1mm at 300 DPI = 11.81 pixels ≈ 12 pixels
|
|
747
|
-
dpi = kwargs.get(
|
|
1303
|
+
dpi = kwargs.get("dpi", 300)
|
|
748
1304
|
margin_px = int(crop_margin_mm * dpi / 25.4) # 25.4mm per inch
|
|
749
1305
|
|
|
750
|
-
# Crop the saved image in place
|
|
751
|
-
|
|
1306
|
+
# Crop the saved image in place, get crop offset for metadata adjustment
|
|
1307
|
+
_, crop_offset = crop(
|
|
1308
|
+
spath,
|
|
1309
|
+
output_path=spath,
|
|
1310
|
+
margin=margin_px,
|
|
1311
|
+
overwrite=True,
|
|
1312
|
+
verbose=False,
|
|
1313
|
+
return_offset=True,
|
|
1314
|
+
)
|
|
1315
|
+
|
|
1316
|
+
# Adjust axes_bbox_px in metadata to account for crop offset
|
|
1317
|
+
if crop_offset and collected_metadata:
|
|
1318
|
+
if "axes_bbox_px" in collected_metadata:
|
|
1319
|
+
bbox = collected_metadata["axes_bbox_px"]
|
|
1320
|
+
# Subtract crop offset from all coordinates
|
|
1321
|
+
# left/upper is where the crop started
|
|
1322
|
+
left_offset = crop_offset["left"]
|
|
1323
|
+
upper_offset = crop_offset["upper"]
|
|
1324
|
+
bbox["x0"] = bbox.get("x0", 0) - left_offset
|
|
1325
|
+
bbox["x1"] = bbox.get("x1", 0) - left_offset
|
|
1326
|
+
bbox["y0"] = bbox.get("y0", 0) - upper_offset
|
|
1327
|
+
bbox["y1"] = bbox.get("y1", 0) - upper_offset
|
|
1328
|
+
# Update width/height to match new image size
|
|
1329
|
+
# (bbox width/height shouldn't change, but figure size does)
|
|
1330
|
+
|
|
1331
|
+
# Also update figure size in metadata
|
|
1332
|
+
if "figure" in collected_metadata:
|
|
1333
|
+
fig_meta = collected_metadata["figure"]
|
|
1334
|
+
if "size_px" in fig_meta:
|
|
1335
|
+
fig_meta["size_px"] = [
|
|
1336
|
+
crop_offset["new_width"],
|
|
1337
|
+
crop_offset["new_height"],
|
|
1338
|
+
]
|
|
1339
|
+
if "dimensions" in collected_metadata:
|
|
1340
|
+
dim_meta = collected_metadata["dimensions"]
|
|
1341
|
+
if "figure_size_px" in dim_meta:
|
|
1342
|
+
dim_meta["figure_size_px"] = [
|
|
1343
|
+
crop_offset["new_width"],
|
|
1344
|
+
crop_offset["new_height"],
|
|
1345
|
+
]
|
|
752
1346
|
|
|
753
1347
|
if verbose:
|
|
754
|
-
logger.info(
|
|
1348
|
+
logger.info(
|
|
1349
|
+
f" • Auto-cropped with {crop_margin_mm}mm margin ({margin_px}px at {dpi} DPI)"
|
|
1350
|
+
)
|
|
755
1351
|
|
|
756
1352
|
except Exception as e:
|
|
757
|
-
|
|
758
|
-
warnings.warn(f"Auto-crop failed: {e}. Image saved without cropping.")
|
|
1353
|
+
logger.warning(f"Auto-crop failed: {e}. Image saved without cropping.")
|
|
759
1354
|
|
|
760
1355
|
# Handle separate legend saving
|
|
761
1356
|
_save_separate_legends(
|
|
@@ -772,11 +1367,12 @@ def _handle_image_with_csv(
|
|
|
772
1367
|
|
|
773
1368
|
# Check if the path contains an image extension directory (e.g., ./png/, ./jpg/)
|
|
774
1369
|
# If so, save CSV in a parallel ./csv/ directory
|
|
775
|
-
image_extensions = [
|
|
1370
|
+
image_extensions = ["png", "jpg", "jpeg", "gif", "tiff", "tif", "svg", "pdf"]
|
|
776
1371
|
parent_dir = _os.path.dirname(spath)
|
|
777
1372
|
parent_name = _os.path.basename(parent_dir)
|
|
778
1373
|
filename_without_ext = _os.path.splitext(_os.path.basename(spath))[0]
|
|
779
1374
|
|
|
1375
|
+
csv_path = None # Initialize to avoid UnboundLocalError when CSV export is skipped
|
|
780
1376
|
try:
|
|
781
1377
|
# Get the figure object that may contain plot data
|
|
782
1378
|
fig_obj = _get_figure_with_data(obj)
|
|
@@ -792,7 +1388,9 @@ def _handle_image_with_csv(
|
|
|
792
1388
|
# Create parallel csv/ directory
|
|
793
1389
|
grandparent_dir = _os.path.dirname(parent_dir)
|
|
794
1390
|
csv_dir = _os.path.join(grandparent_dir, "csv")
|
|
795
|
-
csv_path = _os.path.join(
|
|
1391
|
+
csv_path = _os.path.join(
|
|
1392
|
+
csv_dir, filename_without_ext + ".csv"
|
|
1393
|
+
)
|
|
796
1394
|
else:
|
|
797
1395
|
# Save CSV in same directory as image
|
|
798
1396
|
csv_path = _os.path.splitext(spath)[0] + ".csv"
|
|
@@ -810,20 +1408,59 @@ def _handle_image_with_csv(
|
|
|
810
1408
|
no_csv=True,
|
|
811
1409
|
)
|
|
812
1410
|
|
|
1411
|
+
# Update metadata with actual CSV info (after export)
|
|
1412
|
+
# This ensures column names match exactly, including any
|
|
1413
|
+
# deduplication suffixes added by pandas
|
|
1414
|
+
if collected_metadata is not None:
|
|
1415
|
+
try:
|
|
1416
|
+
from scitex.plt.utils._collect_figure_metadata import (
|
|
1417
|
+
_compute_csv_hash,
|
|
1418
|
+
)
|
|
1419
|
+
|
|
1420
|
+
# Ensure data section exists
|
|
1421
|
+
if "data" not in collected_metadata:
|
|
1422
|
+
collected_metadata["data"] = {}
|
|
1423
|
+
|
|
1424
|
+
# Get actual column names from exported DataFrame
|
|
1425
|
+
actual_columns = list(csv_data.columns)
|
|
1426
|
+
|
|
1427
|
+
# Update data section with csv_path (relative to JSON)
|
|
1428
|
+
# Since JSON and CSV are in the same or parallel directories,
|
|
1429
|
+
# use just the filename for simplicity
|
|
1430
|
+
collected_metadata["data"]["csv_path"] = _os.path.basename(csv_path)
|
|
1431
|
+
|
|
1432
|
+
# Update columns to use flat list of actual columns
|
|
1433
|
+
collected_metadata["data"]["columns_actual"] = actual_columns
|
|
1434
|
+
|
|
1435
|
+
# Compute hash of actual CSV data
|
|
1436
|
+
collected_metadata["data"]["csv_hash"] = _compute_csv_hash(
|
|
1437
|
+
csv_data
|
|
1438
|
+
)
|
|
1439
|
+
except Exception:
|
|
1440
|
+
pass # Silently continue if update fails
|
|
1441
|
+
|
|
813
1442
|
# Create symlink_to for CSV if it was specified for the image
|
|
814
1443
|
if symlink_to:
|
|
815
1444
|
# Apply same directory transformation for symlink
|
|
816
1445
|
symlink_parent_dir = _os.path.dirname(symlink_to)
|
|
817
1446
|
symlink_parent_name = _os.path.basename(symlink_parent_dir)
|
|
818
|
-
symlink_filename_without_ext = _os.path.splitext(
|
|
1447
|
+
symlink_filename_without_ext = _os.path.splitext(
|
|
1448
|
+
_os.path.basename(symlink_to)
|
|
1449
|
+
)[0]
|
|
819
1450
|
|
|
820
1451
|
if symlink_parent_name.lower() in image_extensions:
|
|
821
|
-
symlink_grandparent_dir = _os.path.dirname(
|
|
1452
|
+
symlink_grandparent_dir = _os.path.dirname(
|
|
1453
|
+
symlink_parent_dir
|
|
1454
|
+
)
|
|
822
1455
|
csv_symlink_to = _os.path.join(
|
|
823
|
-
symlink_grandparent_dir,
|
|
1456
|
+
symlink_grandparent_dir,
|
|
1457
|
+
"csv",
|
|
1458
|
+
symlink_filename_without_ext + ".csv",
|
|
824
1459
|
)
|
|
825
1460
|
else:
|
|
826
|
-
csv_symlink_to =
|
|
1461
|
+
csv_symlink_to = (
|
|
1462
|
+
_os.path.splitext(symlink_to)[0] + ".csv"
|
|
1463
|
+
)
|
|
827
1464
|
|
|
828
1465
|
_symlink_to(csv_path, csv_symlink_to, True)
|
|
829
1466
|
|
|
@@ -842,14 +1479,24 @@ def _handle_image_with_csv(
|
|
|
842
1479
|
]
|
|
843
1480
|
if isinstance(original_path, str):
|
|
844
1481
|
# Apply same directory transformation for symlink
|
|
845
|
-
orig_parent_dir = _os.path.dirname(
|
|
846
|
-
|
|
847
|
-
|
|
1482
|
+
orig_parent_dir = _os.path.dirname(
|
|
1483
|
+
original_path
|
|
1484
|
+
)
|
|
1485
|
+
orig_parent_name = _os.path.basename(
|
|
1486
|
+
orig_parent_dir
|
|
1487
|
+
)
|
|
1488
|
+
orig_filename_without_ext = _os.path.splitext(
|
|
1489
|
+
_os.path.basename(original_path)
|
|
1490
|
+
)[0]
|
|
848
1491
|
|
|
849
1492
|
if orig_parent_name.lower() in image_extensions:
|
|
850
|
-
orig_grandparent_dir = _os.path.dirname(
|
|
1493
|
+
orig_grandparent_dir = _os.path.dirname(
|
|
1494
|
+
orig_parent_dir
|
|
1495
|
+
)
|
|
851
1496
|
csv_relative = _os.path.join(
|
|
852
|
-
orig_grandparent_dir,
|
|
1497
|
+
orig_grandparent_dir,
|
|
1498
|
+
"csv",
|
|
1499
|
+
orig_filename_without_ext + ".csv",
|
|
853
1500
|
)
|
|
854
1501
|
else:
|
|
855
1502
|
csv_relative = original_path.replace(
|
|
@@ -865,9 +1512,7 @@ def _handle_image_with_csv(
|
|
|
865
1512
|
else:
|
|
866
1513
|
# Fallback to basename if we can't find the original path
|
|
867
1514
|
csv_cwd = (
|
|
868
|
-
_os.getcwd()
|
|
869
|
-
+ "/"
|
|
870
|
-
+ _os.path.basename(csv_path)
|
|
1515
|
+
_os.getcwd() + "/" + _os.path.basename(csv_path)
|
|
871
1516
|
)
|
|
872
1517
|
_symlink(csv_path, csv_cwd, True, True)
|
|
873
1518
|
|
|
@@ -879,14 +1524,18 @@ def _handle_image_with_csv(
|
|
|
879
1524
|
if parent_name.lower() in image_extensions:
|
|
880
1525
|
grandparent_dir = _os.path.dirname(parent_dir)
|
|
881
1526
|
csv_dir = _os.path.join(grandparent_dir, "csv")
|
|
882
|
-
csv_sigmaplot_path = _os.path.join(
|
|
1527
|
+
csv_sigmaplot_path = _os.path.join(
|
|
1528
|
+
csv_dir, filename_without_ext + "_for_sigmaplot.csv"
|
|
1529
|
+
)
|
|
883
1530
|
else:
|
|
884
1531
|
csv_sigmaplot_path = spath.replace(
|
|
885
1532
|
ext_wo_dot, "csv"
|
|
886
1533
|
).replace(".csv", "_for_sigmaplot.csv")
|
|
887
1534
|
|
|
888
1535
|
# Ensure parent directory exists
|
|
889
|
-
_os.makedirs(
|
|
1536
|
+
_os.makedirs(
|
|
1537
|
+
_os.path.dirname(csv_sigmaplot_path), exist_ok=True
|
|
1538
|
+
)
|
|
890
1539
|
# Save directly using _save to avoid path doubling
|
|
891
1540
|
# Don't pass image-specific kwargs to CSV save
|
|
892
1541
|
_save(
|
|
@@ -902,12 +1551,18 @@ def _handle_image_with_csv(
|
|
|
902
1551
|
if symlink_to:
|
|
903
1552
|
symlink_parent_dir = _os.path.dirname(symlink_to)
|
|
904
1553
|
symlink_parent_name = _os.path.basename(symlink_parent_dir)
|
|
905
|
-
symlink_filename_without_ext = _os.path.splitext(
|
|
1554
|
+
symlink_filename_without_ext = _os.path.splitext(
|
|
1555
|
+
_os.path.basename(symlink_to)
|
|
1556
|
+
)[0]
|
|
906
1557
|
|
|
907
1558
|
if symlink_parent_name.lower() in image_extensions:
|
|
908
|
-
symlink_grandparent_dir = _os.path.dirname(
|
|
1559
|
+
symlink_grandparent_dir = _os.path.dirname(
|
|
1560
|
+
symlink_parent_dir
|
|
1561
|
+
)
|
|
909
1562
|
csv_sigmaplot_symlink_to = _os.path.join(
|
|
910
|
-
symlink_grandparent_dir,
|
|
1563
|
+
symlink_grandparent_dir,
|
|
1564
|
+
"csv",
|
|
1565
|
+
symlink_filename_without_ext + "_for_sigmaplot.csv",
|
|
911
1566
|
)
|
|
912
1567
|
else:
|
|
913
1568
|
csv_sigmaplot_symlink_to = (
|
|
@@ -930,12 +1585,7 @@ def _handle_image_with_csv(
|
|
|
930
1585
|
)
|
|
931
1586
|
_symlink(csv_sigmaplot_path, csv_cwd, True, True)
|
|
932
1587
|
except Exception as e:
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
warnings.warn(f"CSV export failed: {e}")
|
|
936
|
-
import traceback
|
|
937
|
-
|
|
938
|
-
traceback.print_exc()
|
|
1588
|
+
logger.warning(f"CSV export failed: {e}")
|
|
939
1589
|
|
|
940
1590
|
# Save metadata as JSON if collected
|
|
941
1591
|
if collected_metadata is not None and not dry_run:
|
|
@@ -944,7 +1594,16 @@ def _handle_image_with_csv(
|
|
|
944
1594
|
# If so, save JSON in a parallel ./json/ directory
|
|
945
1595
|
# Example: ./path/to/output/png/fig.png -> ./path/to/output/json/fig.json
|
|
946
1596
|
# Example: ./path/to/output/fig.png -> ./path/to/output/fig.json (same dir)
|
|
947
|
-
image_extensions = [
|
|
1597
|
+
image_extensions = [
|
|
1598
|
+
"png",
|
|
1599
|
+
"jpg",
|
|
1600
|
+
"jpeg",
|
|
1601
|
+
"gif",
|
|
1602
|
+
"tiff",
|
|
1603
|
+
"tif",
|
|
1604
|
+
"svg",
|
|
1605
|
+
"pdf",
|
|
1606
|
+
]
|
|
948
1607
|
parent_dir = _os.path.dirname(spath)
|
|
949
1608
|
parent_name = _os.path.basename(parent_dir)
|
|
950
1609
|
filename_without_ext = _os.path.splitext(_os.path.basename(spath))[0]
|
|
@@ -972,17 +1631,29 @@ def _handle_image_with_csv(
|
|
|
972
1631
|
no_csv=True,
|
|
973
1632
|
)
|
|
974
1633
|
|
|
1634
|
+
# Verify CSV/JSON consistency (data_ref must match columns_actual)
|
|
1635
|
+
# Only check for verbose schema - recipe/editable schemas use different data_ref structure
|
|
1636
|
+
if csv_path and not dry_run and json_schema == "verbose":
|
|
1637
|
+
from scitex.plt.utils._collect_figure_metadata import (
|
|
1638
|
+
assert_csv_json_consistency,
|
|
1639
|
+
)
|
|
1640
|
+
assert_csv_json_consistency(csv_path, json_path)
|
|
1641
|
+
|
|
975
1642
|
# Create symlink_to for JSON if it was specified for the image
|
|
976
1643
|
if symlink_to:
|
|
977
1644
|
# Apply same directory transformation for symlink
|
|
978
1645
|
symlink_parent_dir = _os.path.dirname(symlink_to)
|
|
979
1646
|
symlink_parent_name = _os.path.basename(symlink_parent_dir)
|
|
980
|
-
symlink_filename_without_ext = _os.path.splitext(
|
|
1647
|
+
symlink_filename_without_ext = _os.path.splitext(
|
|
1648
|
+
_os.path.basename(symlink_to)
|
|
1649
|
+
)[0]
|
|
981
1650
|
|
|
982
1651
|
if symlink_parent_name.lower() in image_extensions:
|
|
983
1652
|
symlink_grandparent_dir = _os.path.dirname(symlink_parent_dir)
|
|
984
1653
|
json_symlink_to = _os.path.join(
|
|
985
|
-
symlink_grandparent_dir,
|
|
1654
|
+
symlink_grandparent_dir,
|
|
1655
|
+
"json",
|
|
1656
|
+
symlink_filename_without_ext + ".json",
|
|
986
1657
|
)
|
|
987
1658
|
else:
|
|
988
1659
|
json_symlink_to = _os.path.splitext(symlink_to)[0] + ".json"
|
|
@@ -1004,12 +1675,16 @@ def _handle_image_with_csv(
|
|
|
1004
1675
|
# Apply same directory transformation for symlink
|
|
1005
1676
|
orig_parent_dir = _os.path.dirname(original_path)
|
|
1006
1677
|
orig_parent_name = _os.path.basename(orig_parent_dir)
|
|
1007
|
-
orig_filename_without_ext = _os.path.splitext(
|
|
1678
|
+
orig_filename_without_ext = _os.path.splitext(
|
|
1679
|
+
_os.path.basename(original_path)
|
|
1680
|
+
)[0]
|
|
1008
1681
|
|
|
1009
1682
|
if orig_parent_name.lower() in image_extensions:
|
|
1010
1683
|
orig_grandparent_dir = _os.path.dirname(orig_parent_dir)
|
|
1011
1684
|
json_relative = _os.path.join(
|
|
1012
|
-
orig_grandparent_dir,
|
|
1685
|
+
orig_grandparent_dir,
|
|
1686
|
+
"json",
|
|
1687
|
+
orig_filename_without_ext + ".json",
|
|
1013
1688
|
)
|
|
1014
1689
|
else:
|
|
1015
1690
|
json_relative = original_path.replace(
|
|
@@ -1025,16 +1700,16 @@ def _handle_image_with_csv(
|
|
|
1025
1700
|
json_cwd = _os.getcwd() + "/" + _os.path.basename(json_path)
|
|
1026
1701
|
_symlink(json_path, json_cwd, True, True)
|
|
1027
1702
|
|
|
1703
|
+
except AssertionError:
|
|
1704
|
+
# Re-raise assertion errors - these are validation failures that should stop execution
|
|
1705
|
+
raise
|
|
1028
1706
|
except Exception as e:
|
|
1029
|
-
|
|
1030
|
-
warnings.warn(f"JSON metadata export failed: {e}")
|
|
1031
|
-
import traceback
|
|
1032
|
-
traceback.print_exc()
|
|
1707
|
+
logger.warning(f"JSON metadata export failed: {e}")
|
|
1033
1708
|
|
|
1034
1709
|
|
|
1035
1710
|
# Dispatch dictionary for O(1) file format lookup
|
|
1036
1711
|
_FILE_HANDLERS = {
|
|
1037
|
-
# Canvas directory format (scitex.
|
|
1712
|
+
# Canvas directory format (scitex.fig)
|
|
1038
1713
|
".canvas": save_canvas,
|
|
1039
1714
|
# Excel formats
|
|
1040
1715
|
".xlsx": save_excel,
|