scitex 2.7.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 +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/fig/__init__.py +352 -0
- scitex/{vis → fig}/backend/_parser.py +1 -1
- scitex/{vis → fig}/canvas.py +1 -1
- scitex/{vis → fig}/editor/_defaults.py +70 -5
- scitex/fig/editor/_edit.py +751 -0
- scitex/{vis → fig}/editor/_qt_editor.py +181 -1
- scitex/fig/editor/flask_editor/_bbox.py +1276 -0
- scitex/fig/editor/flask_editor/_core.py +624 -0
- scitex/{vis → fig}/editor/flask_editor/_plotter.py +38 -4
- scitex/fig/editor/flask_editor/_renderer.py +739 -0
- scitex/{vis → fig}/editor/flask_editor/templates/__init__.py +1 -1
- scitex/fig/editor/flask_editor/templates/_html.py +834 -0
- scitex/fig/editor/flask_editor/templates/_scripts.py +3136 -0
- scitex/{vis → fig}/editor/flask_editor/templates/_styles.py +625 -18
- scitex/{vis → fig}/io/__init__.py +13 -1
- scitex/fig/io/_bundle.py +973 -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 +10 -26
- scitex/io/_bundle.py +434 -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/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.7.3.dist-info}/METADATA +11 -1
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/RECORD +238 -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/_html.py +0 -513
- scitex/vis/editor/flask_editor/templates/_scripts.py +0 -1261
- /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/__init__.py +0 -0
- /scitex/{vis → fig}/editor/_dearpygui_editor.py +0 -0
- /scitex/{vis → fig}/editor/_flask_editor.py +0 -0
- /scitex/{vis → fig}/editor/_mpl_editor.py +0 -0
- /scitex/{vis → fig}/editor/_tkinter_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.7.3.dist-info}/WHEEL +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.0.dist-info → scitex-2.7.3.dist-info}/licenses/LICENSE +0 -0
scitex/io/_load.py
CHANGED
|
@@ -48,6 +48,98 @@ from ._load_modules._zarr import _load_zarr
|
|
|
48
48
|
from ._load_modules._canvas import _load_canvas
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _load_bundle(lpath, verbose=False, **kwargs):
|
|
52
|
+
"""Load a .pltz, .figz, or .statsz bundle.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
lpath : str or Path
|
|
57
|
+
Path to the bundle (directory or ZIP).
|
|
58
|
+
verbose : bool
|
|
59
|
+
If True, print verbose output.
|
|
60
|
+
**kwargs
|
|
61
|
+
Additional arguments.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
For .pltz bundles:
|
|
66
|
+
tuple: (fig, ax, data) where fig is reconstructed figure,
|
|
67
|
+
ax is the axes, data is DataFrame or None.
|
|
68
|
+
For .figz bundles:
|
|
69
|
+
dict: Figure data with 'spec' and 'panels'.
|
|
70
|
+
For .statsz bundles:
|
|
71
|
+
dict: Stats data with 'spec' and 'comparisons'.
|
|
72
|
+
"""
|
|
73
|
+
from ._bundle import load_bundle, BundleType
|
|
74
|
+
|
|
75
|
+
bundle = load_bundle(lpath)
|
|
76
|
+
bundle_type = bundle.get('type')
|
|
77
|
+
|
|
78
|
+
if bundle_type == BundleType.PLTZ:
|
|
79
|
+
# Return (fig, ax, data) tuple for .pltz bundles
|
|
80
|
+
# Note: We return the spec and data, not a reconstructed figure
|
|
81
|
+
# as matplotlib figures cannot be perfectly serialized/deserialized
|
|
82
|
+
import matplotlib.pyplot as plt
|
|
83
|
+
from pathlib import Path
|
|
84
|
+
|
|
85
|
+
p = Path(lpath)
|
|
86
|
+
bundle_dir = p
|
|
87
|
+
|
|
88
|
+
# Handle ZIP extraction
|
|
89
|
+
if not p.is_dir():
|
|
90
|
+
import tempfile
|
|
91
|
+
import zipfile
|
|
92
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
93
|
+
with zipfile.ZipFile(p, 'r') as zf:
|
|
94
|
+
zf.extractall(temp_dir)
|
|
95
|
+
bundle_dir = temp_dir
|
|
96
|
+
|
|
97
|
+
# Find PNG file - layered format stores in exports/
|
|
98
|
+
basename = bundle.get('basename', 'plot')
|
|
99
|
+
png_path = bundle_dir / "exports" / f"{basename}.png"
|
|
100
|
+
if not png_path.exists():
|
|
101
|
+
# Fallback to root level (legacy format)
|
|
102
|
+
png_path = bundle_dir / f"{basename}.png"
|
|
103
|
+
|
|
104
|
+
# Load the PNG as a figure
|
|
105
|
+
if png_path.exists():
|
|
106
|
+
img = plt.imread(str(png_path))
|
|
107
|
+
fig, ax = plt.subplots()
|
|
108
|
+
ax.imshow(img)
|
|
109
|
+
ax.axis('off')
|
|
110
|
+
|
|
111
|
+
# Attach metadata from spec
|
|
112
|
+
spec = bundle.get('spec', {})
|
|
113
|
+
if spec:
|
|
114
|
+
# Handle both layered and legacy spec formats
|
|
115
|
+
axes_list = spec.get('axes', [])
|
|
116
|
+
if axes_list and isinstance(axes_list, list):
|
|
117
|
+
for key, val in axes_list[0].items():
|
|
118
|
+
setattr(ax, f'_scitex_{key}', val)
|
|
119
|
+
# Theme from style (layered) or spec (legacy)
|
|
120
|
+
style = bundle.get('style', {})
|
|
121
|
+
theme = style.get('theme', {}) if style else spec.get('theme', {})
|
|
122
|
+
if theme:
|
|
123
|
+
fig._scitex_theme = theme.get('mode')
|
|
124
|
+
|
|
125
|
+
# Data from bundle (merged in load_layered_pltz_bundle)
|
|
126
|
+
data = bundle.get('data')
|
|
127
|
+
return fig, ax, data
|
|
128
|
+
else:
|
|
129
|
+
# No PNG, return spec and data
|
|
130
|
+
return bundle.get('spec'), None, bundle.get('data')
|
|
131
|
+
|
|
132
|
+
elif bundle_type == BundleType.FIGZ:
|
|
133
|
+
# Return figure dict for .figz bundles
|
|
134
|
+
return bundle
|
|
135
|
+
|
|
136
|
+
elif bundle_type == BundleType.STATSZ:
|
|
137
|
+
# Return stats dict for .statsz bundles
|
|
138
|
+
return bundle
|
|
139
|
+
|
|
140
|
+
return bundle
|
|
141
|
+
|
|
142
|
+
|
|
51
143
|
def load(
|
|
52
144
|
lpath: Union[str, Path],
|
|
53
145
|
ext: str = None,
|
|
@@ -157,6 +249,12 @@ def load(
|
|
|
157
249
|
if lpath.endswith(".canvas"):
|
|
158
250
|
return _load_canvas(lpath, verbose=verbose, **kwargs)
|
|
159
251
|
|
|
252
|
+
# Handle bundle formats (.pltz, .figz, .statsz and their .d variants)
|
|
253
|
+
bundle_extensions = (".figz", ".pltz", ".statsz")
|
|
254
|
+
for bext in bundle_extensions:
|
|
255
|
+
if lpath.endswith(bext) or lpath.endswith(f"{bext}.d"):
|
|
256
|
+
return _load_bundle(lpath, verbose=verbose, **kwargs)
|
|
257
|
+
|
|
160
258
|
# Check if it's a glob pattern
|
|
161
259
|
if "*" in lpath or "?" in lpath or "[" in lpath:
|
|
162
260
|
# Handle glob pattern
|
|
@@ -11,7 +11,6 @@ __DIR__ = os.path.dirname(__FILE__)
|
|
|
11
11
|
|
|
12
12
|
import random
|
|
13
13
|
import time
|
|
14
|
-
import warnings
|
|
15
14
|
|
|
16
15
|
# Time-stamp: "2025-06-13 21:00:00 (ywatanabe)"
|
|
17
16
|
|
|
@@ -22,6 +21,10 @@ from typing import Any, Dict, List, Optional
|
|
|
22
21
|
import h5py
|
|
23
22
|
import numpy as np
|
|
24
23
|
|
|
24
|
+
from scitex import logging
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
class H5Explorer:
|
|
27
30
|
"""Interactive HDF5 file explorer.
|
|
@@ -262,7 +265,7 @@ def explore_h5(filepath: str) -> None:
|
|
|
262
265
|
explorer.explore()
|
|
263
266
|
explorer.close()
|
|
264
267
|
else:
|
|
265
|
-
|
|
268
|
+
logger.warning(f"File does not exist: {filepath}")
|
|
266
269
|
|
|
267
270
|
|
|
268
271
|
def has_h5_key(h5_path, key, max_retries=3, action_on_corrupted="delete"):
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Timestamp: 2025-12-08
|
|
4
4
|
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_load_modules/_canvas.py
|
|
5
5
|
"""
|
|
6
|
-
Load canvas directory (.canvas) for scitex.
|
|
6
|
+
Load canvas directory (.canvas) for scitex.fig.
|
|
7
7
|
|
|
8
8
|
Canvas directories are portable figure bundles containing:
|
|
9
9
|
- canvas.json: Layout, panels, composition settings
|
|
@@ -100,7 +100,7 @@ def _load_canvas(
|
|
|
100
100
|
# Return Canvas object by default
|
|
101
101
|
if not as_dict:
|
|
102
102
|
try:
|
|
103
|
-
from scitex.
|
|
103
|
+
from scitex.fig.canvas import Canvas
|
|
104
104
|
|
|
105
105
|
canvas_obj = Canvas.from_dict(canvas_dict)
|
|
106
106
|
# Store reference to original directory for copying
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
# Time-stamp: "2024-11-14 07:55:38 (ywatanabe)"
|
|
4
4
|
# File: ./scitex_repo/src/scitex/io/_load_modules/_image.py
|
|
5
5
|
|
|
6
|
-
import logging
|
|
7
6
|
from typing import Any, Dict, Optional, Tuple, Union
|
|
8
7
|
|
|
9
8
|
from PIL import Image
|
|
10
9
|
|
|
10
|
+
from scitex import logging
|
|
11
|
+
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -58,9 +59,7 @@ def _load_image(
|
|
|
58
59
|
|
|
59
60
|
except Exception as e:
|
|
60
61
|
# If metadata reading fails, return None
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
warnings.warn(f"Failed to read metadata: {e}")
|
|
62
|
+
logger.warning(f"Failed to read metadata: {e}")
|
|
64
63
|
return img, None
|
|
65
64
|
|
|
66
65
|
|
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
|