scitex 2.7.0__py3-none-any.whl → 2.8.1__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 +6 -2
- scitex/__version__.py +1 -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/bridge/_helpers.py +1 -1
- scitex/bridge/_plt_vis.py +1 -1
- scitex/bridge/_stats_vis.py +1 -1
- 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/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/fig/__init__.py +352 -0
- scitex/{vis → fig}/backend/_parser.py +1 -1
- scitex/{vis → fig}/canvas.py +1 -1
- scitex/{vis → fig}/editor/__init__.py +5 -2
- scitex/{vis → fig}/editor/_dearpygui_editor.py +1 -1
- scitex/{vis → fig}/editor/_defaults.py +70 -5
- scitex/{vis → fig}/editor/_mpl_editor.py +1 -1
- scitex/{vis → fig}/editor/_qt_editor.py +182 -2
- scitex/{vis → fig}/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +1299 -0
- scitex/fig/editor/flask_editor/_core.py +1429 -0
- scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
- scitex/fig/editor/flask_editor/_renderer.py +813 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +123 -0
- scitex/fig/editor/flask_editor/templates/_html.py +852 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +4933 -0
- scitex/fig/editor/flask_editor/templates/_styles.py +1658 -0
- scitex/{vis → fig}/io/__init__.py +13 -1
- scitex/fig/io/_bundle.py +1058 -0
- scitex/{vis → fig}/io/_canvas.py +1 -1
- scitex/{vis → fig}/io/_data.py +1 -1
- scitex/{vis → fig}/io/_export.py +1 -1
- scitex/{vis → fig}/io/_load.py +1 -1
- scitex/{vis → fig}/io/_panel.py +1 -1
- scitex/{vis → fig}/io/_save.py +1 -1
- scitex/{vis → fig}/model/__init__.py +1 -1
- scitex/{vis → fig}/model/_annotations.py +1 -1
- scitex/{vis → fig}/model/_axes.py +1 -1
- scitex/{vis → fig}/model/_figure.py +1 -1
- scitex/{vis → fig}/model/_guides.py +1 -1
- scitex/{vis → fig}/model/_plot.py +1 -1
- scitex/{vis → fig}/model/_styles.py +1 -1
- scitex/{vis → fig}/utils/__init__.py +1 -1
- scitex/io/__init__.py +22 -26
- scitex/io/_bundle.py +493 -0
- scitex/io/_flush.py +5 -2
- scitex/io/_load.py +98 -0
- scitex/io/_load_modules/_H5Explorer.py +5 -2
- scitex/io/_load_modules/_canvas.py +2 -2
- scitex/io/_load_modules/_image.py +3 -4
- scitex/io/_load_modules/_txt.py +4 -2
- scitex/io/_metadata.py +34 -324
- 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 +5 -3
- scitex/io/_save.py +548 -30
- scitex/io/_save_modules/_canvas.py +3 -3
- scitex/io/_save_modules/_image.py +5 -9
- scitex/io/_save_modules/_tex.py +7 -4
- scitex/io/_zip_bundle.py +439 -0
- scitex/io/utils/h5_to_zarr.py +11 -9
- 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/plt/__init__.py +5 -2
- scitex/plt/_subplots/_AxesWrapper.py +6 -6
- scitex/plt/_subplots/_AxisWrapper.py +15 -9
- 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/__init__.py +79 -5
- scitex/plt/_subplots/_FigWrapper.py +6 -6
- scitex/plt/_subplots/_SubplotsWrapper.py +28 -18
- scitex/plt/_subplots/_export_as_csv.py +35 -5
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +8 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_annotate.py +10 -21
- scitex/plt/_subplots/_export_as_csv_formatters/_format_eventplot.py +18 -7
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow2d.py +28 -12
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_imshow.py +13 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +12 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_scatter.py +10 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +10 -4
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_jointplot.py +18 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_lineplot.py +44 -36
- scitex/plt/_subplots/_export_as_csv_formatters/_format_sns_pairplot.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +11 -5
- 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 +14 -3
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_contour.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_ecdf.py +14 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +120 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_heatmap.py +16 -6
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_image.py +29 -19
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_imshow.py +63 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_joyplot.py +22 -5
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_ci.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_mean_std.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_median_iqr.py +18 -14
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_raster.py +10 -2
- 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 +18 -9
- scitex/plt/ax/_plot/_stx_ecdf.py +4 -2
- scitex/plt/gallery/_generate.py +421 -14
- 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 +14 -0
- scitex/plt/styles/presets.py +78 -0
- scitex/plt/utils/__init__.py +13 -1
- scitex/plt/utils/_collect_figure_metadata.py +10 -14
- scitex/plt/utils/_configure_mpl.py +6 -18
- scitex/plt/utils/_crop.py +32 -14
- scitex/plt/utils/_csv_column_naming.py +54 -0
- scitex/plt/utils/_figure_mm.py +116 -1
- scitex/plt/utils/_hitmap.py +1643 -0
- scitex/plt/utils/metadata/__init__.py +25 -0
- scitex/plt/utils/metadata/_core.py +9 -10
- scitex/plt/utils/metadata/_dimensions.py +6 -3
- scitex/plt/utils/metadata/_editable_export.py +405 -0
- scitex/plt/utils/metadata/_geometry_extraction.py +570 -0
- scitex/schema/__init__.py +109 -16
- scitex/schema/_canvas.py +1 -1
- scitex/schema/_plot.py +1015 -0
- scitex/schema/_stats.py +2 -2
- scitex/stats/__init__.py +117 -0
- scitex/stats/io/__init__.py +29 -0
- scitex/stats/io/_bundle.py +156 -0
- scitex/tex/__init__.py +4 -0
- scitex/tex/_export.py +890 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/METADATA +11 -1
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/RECORD +294 -170
- scitex/io/memo.md +0 -2827
- scitex/plt/REQUESTS.md +0 -191
- scitex/plt/_subplots/TODO.md +0 -53
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin.py +0 -559
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +0 -1609
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +0 -447
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_between.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/area/fill_betweenx.json +0 -88
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fill_between.json +0 -103
- scitex/plt/templates/research-master/scitex/vis/gallery/area/stx_fillv.json +0 -106
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/bar.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/barh.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/boxplot.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_bar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_barh.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_box.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_boxplot.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violin.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/stx_violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/categorical/violinplot.json +0 -91
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contour.json +0 -97
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/contourf.json +0 -98
- scitex/plt/templates/research-master/scitex/vis/gallery/contour/stx_contour.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist.json +0 -101
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/hist2d.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_ecdf.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_joyplot.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/distribution/stx_kde.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/imshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/matshow.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_conf_mat.json +0 -83
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_heatmap.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_image.json +0 -121
- scitex/plt/templates/research-master/scitex/vis/gallery/grid/stx_imshow.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/line/plot.json +0 -110
- scitex/plt/templates/research-master/scitex/vis/gallery/line/step.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_line.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/line/stx_shaded_line.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/hexbin.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/scatter.json +0 -95
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stem.json +0 -92
- scitex/plt/templates/research-master/scitex/vis/gallery/scatter/stx_scatter.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/special/pie.json +0 -94
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_raster.json +0 -109
- scitex/plt/templates/research-master/scitex/vis/gallery/special/stx_rectangle.json +0 -108
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/errorbar.json +0 -93
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_errorbar.json +0 -84
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_ci.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_mean_std.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/statistical/stx_median_iqr.json +0 -96
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/quiver.json +0 -99
- scitex/plt/templates/research-master/scitex/vis/gallery/vector/streamplot.json +0 -100
- scitex/vis/__init__.py +0 -177
- scitex/vis/editor/_edit.py +0 -390
- scitex/vis/editor/flask_editor/_bbox.py +0 -529
- scitex/vis/editor/flask_editor/_core.py +0 -168
- scitex/vis/editor/flask_editor/_renderer.py +0 -393
- scitex/vis/editor/flask_editor/templates/__init__.py +0 -33
- scitex/vis/editor/flask_editor/templates/_html.py +0 -513
- scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
- scitex/vis/editor/flask_editor/templates/_styles.py +0 -739
- /scitex/{vis → fig}/README.md +0 -0
- /scitex/{vis → fig}/backend/__init__.py +0 -0
- /scitex/{vis → fig}/backend/_export.py +0 -0
- /scitex/{vis → fig}/backend/_render.py +0 -0
- /scitex/{vis → fig}/docs/CANVAS_ARCHITECTURE.md +0 -0
- /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/__init__.py +0 -0
- /scitex/{vis → fig}/editor/flask_editor/_utils.py +0 -0
- /scitex/{vis → fig}/io/_directory.py +0 -0
- /scitex/{vis → fig}/model/_plot_types.py +0 -0
- /scitex/{vis → fig}/utils/_defaults.py +0 -0
- /scitex/{vis → fig}/utils/_validate.py +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.0.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
scitex/io/_load_modules/_txt.py
CHANGED
|
@@ -9,7 +9,9 @@ __FILE__ = __file__
|
|
|
9
9
|
__DIR__ = os.path.dirname(__FILE__)
|
|
10
10
|
# ----------------------------------------
|
|
11
11
|
|
|
12
|
-
import
|
|
12
|
+
from scitex import logging
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
13
15
|
|
|
14
16
|
# # UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8a in position 30173: invalid start byte
|
|
15
17
|
# def _load_txt(lpath, **kwargs):
|
|
@@ -93,7 +95,7 @@ def _load_txt(lpath, strip=True, as_lines=True):
|
|
|
93
95
|
lpath = str(lpath)
|
|
94
96
|
|
|
95
97
|
if not lpath.endswith((".txt", ".log", ".event", ".py", ".sh", ".tex", ".bib")):
|
|
96
|
-
|
|
98
|
+
logger.warning(f"Unexpected extension for file: {lpath}")
|
|
97
99
|
|
|
98
100
|
try:
|
|
99
101
|
with open(lpath, "r", encoding="utf-8") as file:
|
scitex/io/_metadata.py
CHANGED
|
@@ -1,337 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
-
# Timestamp: "2025-11-14 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_metadata.py
|
|
5
|
-
|
|
4
|
+
|
|
6
5
|
"""
|
|
7
6
|
Image and PDF metadata embedding and extraction for research reproducibility.
|
|
8
7
|
|
|
9
|
-
This module
|
|
10
|
-
|
|
8
|
+
This module re-exports from _metadata_modules for backwards compatibility.
|
|
9
|
+
See _metadata_modules/ for format-specific implementations:
|
|
11
10
|
- PNG: tEXt chunks
|
|
12
11
|
- JPEG: EXIF ImageDescription field
|
|
12
|
+
- SVG: <metadata> element with scitex namespace
|
|
13
13
|
- PDF: XMP metadata (industry standard)
|
|
14
|
-
|
|
15
|
-
The metadata is stored as JSON strings, allowing flexible dictionary structures.
|
|
16
14
|
"""
|
|
17
15
|
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# Handle tuple
|
|
49
|
-
if isinstance(obj, tuple):
|
|
50
|
-
return [_convert_for_json(item) for item in obj]
|
|
51
|
-
|
|
52
|
-
# Handle numpy arrays
|
|
53
|
-
if hasattr(obj, "tolist"):
|
|
54
|
-
return obj.tolist()
|
|
55
|
-
|
|
56
|
-
# Default: return as-is
|
|
57
|
-
return obj
|
|
58
|
-
# ----------------------------------------
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def embed_metadata(image_path: str, metadata: Dict[str, Any]) -> None:
|
|
62
|
-
"""
|
|
63
|
-
Embed metadata into an existing image or PDF file.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
image_path: Path to the image/PDF file (PNG, JPEG, or PDF)
|
|
67
|
-
metadata: Dictionary containing metadata (must be JSON serializable)
|
|
68
|
-
|
|
69
|
-
Raises:
|
|
70
|
-
ValueError: If file format is not supported or metadata is not JSON serializable
|
|
71
|
-
FileNotFoundError: If file doesn't exist
|
|
72
|
-
|
|
73
|
-
Example:
|
|
74
|
-
>>> metadata = {
|
|
75
|
-
... 'experiment': 'seizure_prediction_001',
|
|
76
|
-
... 'session': '2024-11-14',
|
|
77
|
-
... 'analysis': 'PAC'
|
|
78
|
-
... }
|
|
79
|
-
>>> embed_metadata('result.png', metadata)
|
|
80
|
-
>>> embed_metadata('result.pdf', metadata)
|
|
81
|
-
"""
|
|
82
|
-
if not os.path.exists(image_path):
|
|
83
|
-
raise FileNotFoundError(f"File not found: {image_path}")
|
|
84
|
-
|
|
85
|
-
# Convert non-serializable objects (e.g., FixedFloat) to serializable format
|
|
86
|
-
metadata = _convert_for_json(metadata)
|
|
87
|
-
|
|
88
|
-
# Serialize metadata to JSON
|
|
89
|
-
try:
|
|
90
|
-
metadata_json = json.dumps(metadata, ensure_ascii=False, indent=2)
|
|
91
|
-
except (TypeError, ValueError) as e:
|
|
92
|
-
raise ValueError(f"Metadata must be JSON serializable: {e}")
|
|
93
|
-
|
|
94
|
-
# Handle PNG format
|
|
95
|
-
if image_path.lower().endswith(".png"):
|
|
96
|
-
# Open the image
|
|
97
|
-
img = Image.open(image_path)
|
|
98
|
-
# Create new PNG info with metadata
|
|
99
|
-
pnginfo = PngInfo()
|
|
100
|
-
pnginfo.add_text("scitex_metadata", metadata_json)
|
|
101
|
-
|
|
102
|
-
# Save with metadata
|
|
103
|
-
img.save(image_path, "PNG", pnginfo=pnginfo)
|
|
104
|
-
|
|
105
|
-
# Handle JPEG format
|
|
106
|
-
elif image_path.lower().endswith((".jpg", ".jpeg")):
|
|
107
|
-
# Open the image
|
|
108
|
-
img = Image.open(image_path)
|
|
109
|
-
|
|
110
|
-
try:
|
|
111
|
-
import piexif
|
|
112
|
-
except ImportError:
|
|
113
|
-
raise ImportError(
|
|
114
|
-
"piexif is required for JPEG metadata support. "
|
|
115
|
-
"Install with: pip install piexif"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
# Convert to RGB if necessary (JPEG doesn't support RGBA)
|
|
119
|
-
if img.mode in ("RGBA", "LA", "P"):
|
|
120
|
-
rgb_img = Image.new("RGB", img.size, (255, 255, 255))
|
|
121
|
-
if img.mode == "P":
|
|
122
|
-
img = img.convert("RGBA")
|
|
123
|
-
if img.mode in ("RGBA", "LA"):
|
|
124
|
-
rgb_img.paste(img, mask=img.split()[-1])
|
|
125
|
-
else:
|
|
126
|
-
rgb_img.paste(img)
|
|
127
|
-
img = rgb_img
|
|
128
|
-
|
|
129
|
-
# Create EXIF dict with metadata in ImageDescription field
|
|
130
|
-
exif_dict = {
|
|
131
|
-
"0th": {piexif.ImageIFD.ImageDescription: metadata_json.encode("utf-8")},
|
|
132
|
-
"Exif": {},
|
|
133
|
-
"GPS": {},
|
|
134
|
-
"1st": {},
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
# Try to preserve existing EXIF data
|
|
138
|
-
try:
|
|
139
|
-
existing_exif = piexif.load(img.info.get("exif", b""))
|
|
140
|
-
# Merge with new metadata (prioritize new metadata)
|
|
141
|
-
for ifd in ["Exif", "GPS", "1st"]:
|
|
142
|
-
if ifd in existing_exif:
|
|
143
|
-
exif_dict[ifd].update(existing_exif[ifd])
|
|
144
|
-
except:
|
|
145
|
-
pass # If existing EXIF is corrupted, just use new metadata
|
|
146
|
-
|
|
147
|
-
exif_bytes = piexif.dump(exif_dict)
|
|
148
|
-
|
|
149
|
-
# Save with EXIF metadata (quality=100 for maximum quality)
|
|
150
|
-
img.save(
|
|
151
|
-
image_path,
|
|
152
|
-
"JPEG",
|
|
153
|
-
quality=100,
|
|
154
|
-
subsampling=0,
|
|
155
|
-
optimize=False,
|
|
156
|
-
exif=exif_bytes,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
# Handle PDF format
|
|
160
|
-
elif image_path.lower().endswith(".pdf"):
|
|
161
|
-
try:
|
|
162
|
-
from pypdf import PdfReader, PdfWriter
|
|
163
|
-
except ImportError:
|
|
164
|
-
raise ImportError(
|
|
165
|
-
"pypdf is required for PDF metadata support. "
|
|
166
|
-
"Install with: pip install pypdf"
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
# Read existing PDF
|
|
170
|
-
reader = PdfReader(image_path)
|
|
171
|
-
writer = PdfWriter()
|
|
172
|
-
|
|
173
|
-
# Copy all pages
|
|
174
|
-
for page in reader.pages:
|
|
175
|
-
writer.add_page(page)
|
|
176
|
-
|
|
177
|
-
# Prepare metadata for PDF Info Dictionary
|
|
178
|
-
pdf_metadata = {
|
|
179
|
-
"/Title": metadata.get("title", ""),
|
|
180
|
-
"/Author": metadata.get("author", ""),
|
|
181
|
-
"/Subject": metadata_json, # Store full JSON in Subject field
|
|
182
|
-
"/Creator": "SciTeX",
|
|
183
|
-
"/Producer": "SciTeX",
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
# Add metadata
|
|
187
|
-
writer.add_metadata(pdf_metadata)
|
|
188
|
-
|
|
189
|
-
# Write back to file
|
|
190
|
-
with open(image_path, "wb") as output_file:
|
|
191
|
-
writer.write(output_file)
|
|
192
|
-
|
|
193
|
-
else:
|
|
194
|
-
raise ValueError(
|
|
195
|
-
f"Unsupported file format: {image_path}. "
|
|
196
|
-
"Only PNG, JPEG, and PDF formats are supported."
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
# Close image if it was opened (not for PDF)
|
|
200
|
-
if image_path.lower().endswith((".png", ".jpg", ".jpeg")):
|
|
201
|
-
img.close()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def read_metadata(image_path: str) -> Optional[Dict[str, Any]]:
|
|
205
|
-
"""
|
|
206
|
-
Read metadata from an image or PDF file.
|
|
207
|
-
|
|
208
|
-
Args:
|
|
209
|
-
image_path: Path to the file (PNG, JPEG, or PDF)
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
Dictionary containing metadata, or None if no metadata found
|
|
213
|
-
|
|
214
|
-
Raises:
|
|
215
|
-
FileNotFoundError: If file doesn't exist
|
|
216
|
-
ValueError: If file format is not supported
|
|
217
|
-
|
|
218
|
-
Example:
|
|
219
|
-
>>> metadata = read_metadata('result.png')
|
|
220
|
-
>>> print(metadata['experiment'])
|
|
221
|
-
'seizure_prediction_001'
|
|
222
|
-
>>> metadata = read_metadata('result.pdf')
|
|
223
|
-
"""
|
|
224
|
-
if not os.path.exists(image_path):
|
|
225
|
-
raise FileNotFoundError(f"File not found: {image_path}")
|
|
226
|
-
|
|
227
|
-
# Don't open PDF files with PIL
|
|
228
|
-
if not image_path.lower().endswith(".pdf"):
|
|
229
|
-
img = Image.open(image_path)
|
|
230
|
-
metadata = None
|
|
231
|
-
|
|
232
|
-
try:
|
|
233
|
-
# Handle PNG format
|
|
234
|
-
if image_path.lower().endswith(".png"):
|
|
235
|
-
# Check for scitex_metadata in PNG info
|
|
236
|
-
if hasattr(img, "info") and "scitex_metadata" in img.info:
|
|
237
|
-
metadata_json = img.info["scitex_metadata"]
|
|
238
|
-
try:
|
|
239
|
-
metadata = json.loads(metadata_json)
|
|
240
|
-
except json.JSONDecodeError:
|
|
241
|
-
# Metadata exists but is not valid JSON
|
|
242
|
-
metadata = {"raw": metadata_json}
|
|
243
|
-
|
|
244
|
-
# Handle JPEG format
|
|
245
|
-
elif image_path.lower().endswith((".jpg", ".jpeg")):
|
|
246
|
-
try:
|
|
247
|
-
import piexif
|
|
248
|
-
|
|
249
|
-
# Load EXIF data
|
|
250
|
-
if "exif" in img.info:
|
|
251
|
-
exif_dict = piexif.load(img.info["exif"])
|
|
252
|
-
|
|
253
|
-
# Try to read ImageDescription field
|
|
254
|
-
if piexif.ImageIFD.ImageDescription in exif_dict.get("0th", {}):
|
|
255
|
-
description = exif_dict["0th"][piexif.ImageIFD.ImageDescription]
|
|
256
|
-
|
|
257
|
-
# Decode bytes to string
|
|
258
|
-
if isinstance(description, bytes):
|
|
259
|
-
description = description.decode("utf-8", errors="ignore")
|
|
260
|
-
|
|
261
|
-
# Try to parse as JSON
|
|
262
|
-
try:
|
|
263
|
-
metadata = json.loads(description)
|
|
264
|
-
except json.JSONDecodeError:
|
|
265
|
-
# If not JSON, return as raw text
|
|
266
|
-
metadata = {"raw": description}
|
|
267
|
-
except ImportError:
|
|
268
|
-
pass # piexif not available, return None
|
|
269
|
-
except Exception:
|
|
270
|
-
pass # EXIF data corrupted or not readable
|
|
271
|
-
|
|
272
|
-
# Handle PDF format
|
|
273
|
-
elif image_path.lower().endswith(".pdf"):
|
|
274
|
-
try:
|
|
275
|
-
from pypdf import PdfReader
|
|
276
|
-
|
|
277
|
-
reader = PdfReader(image_path)
|
|
278
|
-
|
|
279
|
-
# Try to read metadata from PDF Info Dictionary
|
|
280
|
-
if reader.metadata:
|
|
281
|
-
# Check Subject field for JSON metadata
|
|
282
|
-
if "/Subject" in reader.metadata:
|
|
283
|
-
subject = reader.metadata["/Subject"]
|
|
284
|
-
try:
|
|
285
|
-
metadata = json.loads(subject)
|
|
286
|
-
except json.JSONDecodeError:
|
|
287
|
-
# If not JSON, create metadata dict from available fields
|
|
288
|
-
metadata = {
|
|
289
|
-
"title": reader.metadata.get("/Title", ""),
|
|
290
|
-
"author": reader.metadata.get("/Author", ""),
|
|
291
|
-
"subject": subject,
|
|
292
|
-
"creator": reader.metadata.get("/Creator", ""),
|
|
293
|
-
}
|
|
294
|
-
except ImportError:
|
|
295
|
-
pass # pypdf not available, return None
|
|
296
|
-
except Exception:
|
|
297
|
-
pass # PDF metadata corrupted or not readable
|
|
298
|
-
finally:
|
|
299
|
-
# No need to close anything for PDF
|
|
300
|
-
pass
|
|
301
|
-
|
|
302
|
-
else:
|
|
303
|
-
raise ValueError(
|
|
304
|
-
f"Unsupported file format: {image_path}. "
|
|
305
|
-
"Only PNG, JPEG, and PDF formats are supported."
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
finally:
|
|
309
|
-
# Only close if img was opened (not for PDF)
|
|
310
|
-
if not image_path.lower().endswith(".pdf"):
|
|
311
|
-
img.close()
|
|
312
|
-
|
|
313
|
-
return metadata
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def has_metadata(image_path: str) -> bool:
|
|
317
|
-
"""
|
|
318
|
-
Check if an image file has embedded metadata.
|
|
319
|
-
|
|
320
|
-
Args:
|
|
321
|
-
image_path: Path to the image file
|
|
322
|
-
|
|
323
|
-
Returns:
|
|
324
|
-
True if metadata exists, False otherwise
|
|
325
|
-
|
|
326
|
-
Example:
|
|
327
|
-
>>> if has_metadata('result.png'):
|
|
328
|
-
... print(read_metadata('result.png'))
|
|
329
|
-
"""
|
|
330
|
-
try:
|
|
331
|
-
metadata = read_metadata(image_path)
|
|
332
|
-
return metadata is not None
|
|
333
|
-
except:
|
|
334
|
-
return False
|
|
335
|
-
|
|
16
|
+
from ._metadata_modules import (
|
|
17
|
+
embed_metadata,
|
|
18
|
+
read_metadata,
|
|
19
|
+
has_metadata,
|
|
20
|
+
embed_metadata_png,
|
|
21
|
+
embed_metadata_jpeg,
|
|
22
|
+
embed_metadata_svg,
|
|
23
|
+
embed_metadata_pdf,
|
|
24
|
+
read_metadata_png,
|
|
25
|
+
read_metadata_jpeg,
|
|
26
|
+
read_metadata_svg,
|
|
27
|
+
read_metadata_pdf,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Backwards compatibility alias
|
|
31
|
+
_convert_for_json = None # Removed - use _metadata_modules._utils.convert_for_json
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"embed_metadata",
|
|
35
|
+
"read_metadata",
|
|
36
|
+
"has_metadata",
|
|
37
|
+
"embed_metadata_png",
|
|
38
|
+
"embed_metadata_jpeg",
|
|
39
|
+
"embed_metadata_svg",
|
|
40
|
+
"embed_metadata_pdf",
|
|
41
|
+
"read_metadata_png",
|
|
42
|
+
"read_metadata_jpeg",
|
|
43
|
+
"read_metadata_svg",
|
|
44
|
+
"read_metadata_pdf",
|
|
45
|
+
]
|
|
336
46
|
|
|
337
47
|
# EOF
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_metadata_modules/__init__.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Image and PDF metadata embedding and extraction for research reproducibility.
|
|
7
|
+
|
|
8
|
+
This module provides functions to embed and extract metadata from image and PDF files.
|
|
9
|
+
Metadata is stored using standard formats:
|
|
10
|
+
- PNG: tEXt chunks
|
|
11
|
+
- JPEG: EXIF ImageDescription field
|
|
12
|
+
- SVG: <metadata> element with scitex namespace
|
|
13
|
+
- PDF: XMP metadata (industry standard)
|
|
14
|
+
|
|
15
|
+
The metadata is stored as JSON strings, allowing flexible dictionary structures.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from ._embed import embed_metadata
|
|
19
|
+
from ._read import read_metadata
|
|
20
|
+
from ._utils import has_metadata
|
|
21
|
+
|
|
22
|
+
# Format-specific modules (for direct access if needed)
|
|
23
|
+
from .embed_metadata_png import embed_metadata_png
|
|
24
|
+
from .embed_metadata_jpeg import embed_metadata_jpeg
|
|
25
|
+
from .embed_metadata_svg import embed_metadata_svg
|
|
26
|
+
from .embed_metadata_pdf import embed_metadata_pdf
|
|
27
|
+
from .read_metadata_png import read_metadata_png
|
|
28
|
+
from .read_metadata_jpeg import read_metadata_jpeg
|
|
29
|
+
from .read_metadata_svg import read_metadata_svg
|
|
30
|
+
from .read_metadata_pdf import read_metadata_pdf
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"embed_metadata",
|
|
34
|
+
"read_metadata",
|
|
35
|
+
"has_metadata",
|
|
36
|
+
"embed_metadata_png",
|
|
37
|
+
"embed_metadata_jpeg",
|
|
38
|
+
"embed_metadata_svg",
|
|
39
|
+
"embed_metadata_pdf",
|
|
40
|
+
"read_metadata_png",
|
|
41
|
+
"read_metadata_jpeg",
|
|
42
|
+
"read_metadata_svg",
|
|
43
|
+
"read_metadata_pdf",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# EOF
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_metadata_modules/_embed.py
|
|
4
|
+
|
|
5
|
+
"""Main embed_metadata dispatcher."""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from ._utils import serialize_metadata
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def embed_metadata(image_path: str, metadata: Dict[str, Any]) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Embed metadata into an existing image or PDF file.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
image_path: Path to the image/PDF file (PNG, JPEG, SVG, or PDF)
|
|
19
|
+
metadata: Dictionary containing metadata (must be JSON serializable)
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
ValueError: If file format is not supported or metadata is not JSON serializable
|
|
23
|
+
FileNotFoundError: If file doesn't exist
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> metadata = {
|
|
27
|
+
... 'experiment': 'seizure_prediction_001',
|
|
28
|
+
... 'session': '2024-11-14',
|
|
29
|
+
... 'analysis': 'PAC'
|
|
30
|
+
... }
|
|
31
|
+
>>> embed_metadata('result.png', metadata)
|
|
32
|
+
>>> embed_metadata('result.pdf', metadata)
|
|
33
|
+
"""
|
|
34
|
+
if not os.path.exists(image_path):
|
|
35
|
+
raise FileNotFoundError(f"File not found: {image_path}")
|
|
36
|
+
|
|
37
|
+
# Serialize metadata to JSON
|
|
38
|
+
metadata_json = serialize_metadata(metadata)
|
|
39
|
+
|
|
40
|
+
path_lower = image_path.lower()
|
|
41
|
+
|
|
42
|
+
# Dispatch to format-specific handlers
|
|
43
|
+
if path_lower.endswith(".png"):
|
|
44
|
+
from .embed_metadata_png import embed_metadata_png
|
|
45
|
+
|
|
46
|
+
embed_metadata_png(image_path, metadata_json)
|
|
47
|
+
|
|
48
|
+
elif path_lower.endswith((".jpg", ".jpeg")):
|
|
49
|
+
from .embed_metadata_jpeg import embed_metadata_jpeg
|
|
50
|
+
|
|
51
|
+
embed_metadata_jpeg(image_path, metadata_json)
|
|
52
|
+
|
|
53
|
+
elif path_lower.endswith(".svg"):
|
|
54
|
+
from .embed_metadata_svg import embed_metadata_svg
|
|
55
|
+
|
|
56
|
+
embed_metadata_svg(image_path, metadata_json)
|
|
57
|
+
|
|
58
|
+
elif path_lower.endswith(".pdf"):
|
|
59
|
+
from .embed_metadata_pdf import embed_metadata_pdf
|
|
60
|
+
|
|
61
|
+
embed_metadata_pdf(image_path, metadata_json, metadata)
|
|
62
|
+
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Unsupported file format: {image_path}. "
|
|
66
|
+
"Only PNG, JPEG, SVG, and PDF formats are supported."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# EOF
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_metadata_modules/_read.py
|
|
4
|
+
|
|
5
|
+
"""Main read_metadata dispatcher."""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def read_metadata(image_path: str) -> Optional[Dict[str, Any]]:
|
|
12
|
+
"""
|
|
13
|
+
Read metadata from an image or PDF file.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
image_path: Path to the file (PNG, JPEG, SVG, or PDF)
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Dictionary containing metadata, or None if no metadata found
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
FileNotFoundError: If file doesn't exist
|
|
23
|
+
ValueError: If file format is not supported
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
>>> metadata = read_metadata('result.png')
|
|
27
|
+
>>> print(metadata['experiment'])
|
|
28
|
+
'seizure_prediction_001'
|
|
29
|
+
>>> metadata = read_metadata('result.pdf')
|
|
30
|
+
"""
|
|
31
|
+
if not os.path.exists(image_path):
|
|
32
|
+
raise FileNotFoundError(f"File not found: {image_path}")
|
|
33
|
+
|
|
34
|
+
path_lower = image_path.lower()
|
|
35
|
+
|
|
36
|
+
# Dispatch to format-specific handlers
|
|
37
|
+
if path_lower.endswith(".png"):
|
|
38
|
+
from .read_metadata_png import read_metadata_png
|
|
39
|
+
|
|
40
|
+
return read_metadata_png(image_path)
|
|
41
|
+
|
|
42
|
+
elif path_lower.endswith((".jpg", ".jpeg")):
|
|
43
|
+
from .read_metadata_jpeg import read_metadata_jpeg
|
|
44
|
+
|
|
45
|
+
return read_metadata_jpeg(image_path)
|
|
46
|
+
|
|
47
|
+
elif path_lower.endswith(".svg"):
|
|
48
|
+
from .read_metadata_svg import read_metadata_svg
|
|
49
|
+
|
|
50
|
+
return read_metadata_svg(image_path)
|
|
51
|
+
|
|
52
|
+
elif path_lower.endswith(".pdf"):
|
|
53
|
+
from .read_metadata_pdf import read_metadata_pdf
|
|
54
|
+
|
|
55
|
+
return read_metadata_pdf(image_path)
|
|
56
|
+
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"Unsupported file format: {image_path}. "
|
|
60
|
+
"Only PNG, JPEG, SVG, and PDF formats are supported."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# EOF
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_metadata/_utils.py
|
|
4
|
+
|
|
5
|
+
"""Shared utilities for metadata handling."""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def convert_for_json(obj: Any) -> Any:
|
|
12
|
+
"""
|
|
13
|
+
Convert non-JSON-serializable objects to serializable format.
|
|
14
|
+
|
|
15
|
+
Handles FixedFloat objects from figure metadata by converting
|
|
16
|
+
them to regular floats.
|
|
17
|
+
"""
|
|
18
|
+
# Handle FixedFloat (from _collect_figure_metadata)
|
|
19
|
+
if (
|
|
20
|
+
hasattr(obj, "value")
|
|
21
|
+
and hasattr(obj, "precision")
|
|
22
|
+
and hasattr(obj, "__class__")
|
|
23
|
+
and obj.__class__.__name__ == "FixedFloat"
|
|
24
|
+
):
|
|
25
|
+
return obj.value
|
|
26
|
+
|
|
27
|
+
# Handle dict recursively
|
|
28
|
+
if isinstance(obj, dict):
|
|
29
|
+
return {key: convert_for_json(value) for key, value in obj.items()}
|
|
30
|
+
|
|
31
|
+
# Handle list recursively
|
|
32
|
+
if isinstance(obj, list):
|
|
33
|
+
return [convert_for_json(item) for item in obj]
|
|
34
|
+
|
|
35
|
+
# Handle tuple
|
|
36
|
+
if isinstance(obj, tuple):
|
|
37
|
+
return [convert_for_json(item) for item in obj]
|
|
38
|
+
|
|
39
|
+
# Handle numpy arrays
|
|
40
|
+
if hasattr(obj, "tolist"):
|
|
41
|
+
return obj.tolist()
|
|
42
|
+
|
|
43
|
+
# Default: return as-is
|
|
44
|
+
return obj
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def serialize_metadata(metadata: Dict[str, Any]) -> str:
|
|
48
|
+
"""Serialize metadata to JSON string."""
|
|
49
|
+
metadata = convert_for_json(metadata)
|
|
50
|
+
try:
|
|
51
|
+
return json.dumps(metadata, ensure_ascii=False, indent=2)
|
|
52
|
+
except (TypeError, ValueError) as e:
|
|
53
|
+
raise ValueError(f"Metadata must be JSON serializable: {e}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def has_metadata(image_path: str) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Check if an image file has embedded metadata.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
image_path: Path to the image file
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
True if metadata exists, False otherwise
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> if has_metadata('result.png'):
|
|
68
|
+
... print(read_metadata('result.png'))
|
|
69
|
+
"""
|
|
70
|
+
from ._read import read_metadata
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
metadata = read_metadata(image_path)
|
|
74
|
+
return metadata is not None
|
|
75
|
+
except:
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# EOF
|