streamlit-nightly 1.39.1.dev20241031__py2.py3-none-any.whl → 1.39.1.dev20241102__py2.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.
- streamlit/commands/execution_control.py +7 -2
- streamlit/commands/logo.py +3 -3
- streamlit/commands/navigation.py +48 -3
- streamlit/commands/page_config.py +8 -3
- streamlit/elements/html.py +6 -4
- streamlit/elements/image.py +28 -443
- streamlit/elements/layouts.py +12 -7
- streamlit/elements/lib/image_utils.py +440 -0
- streamlit/elements/markdown.py +6 -0
- streamlit/elements/media.py +22 -9
- streamlit/elements/metric.py +8 -5
- streamlit/elements/progress.py +2 -1
- streamlit/elements/pyplot.py +3 -5
- streamlit/elements/text.py +1 -1
- streamlit/elements/widgets/audio_input.py +12 -11
- streamlit/elements/widgets/button.py +28 -19
- streamlit/elements/widgets/button_group.py +146 -121
- streamlit/elements/widgets/camera_input.py +13 -11
- streamlit/elements/widgets/chat.py +2 -2
- streamlit/elements/widgets/checkbox.py +30 -24
- streamlit/elements/widgets/color_picker.py +15 -13
- streamlit/elements/widgets/file_uploader.py +12 -12
- streamlit/elements/widgets/multiselect.py +33 -31
- streamlit/elements/widgets/number_input.py +15 -12
- streamlit/elements/widgets/radio.py +15 -12
- streamlit/elements/widgets/select_slider.py +15 -12
- streamlit/elements/widgets/selectbox.py +19 -14
- streamlit/elements/widgets/slider.py +15 -12
- streamlit/elements/widgets/text_widgets.py +33 -27
- streamlit/elements/widgets/time_widgets.py +33 -25
- streamlit/hello/{Animation_Demo.py → animation_demo.py} +9 -10
- streamlit/hello/{Dataframe_Demo.py → dataframe_demo.py} +9 -15
- streamlit/hello/{Hello.py → hello.py} +7 -12
- streamlit/hello/{Mapping_Demo.py → mapping_demo.py} +10 -13
- streamlit/hello/{Plotting_Demo.py → plotting_demo.py} +9 -10
- streamlit/hello/streamlit_app.py +24 -6
- streamlit/proto/Image_pb2.pyi +1 -1
- streamlit/static/asset-manifest.json +3 -3
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/3224.925d380f.chunk.js +1 -0
- streamlit/static/static/js/{main.754d974e.js → main.61a89bda.js} +2 -2
- {streamlit_nightly-1.39.1.dev20241031.dist-info → streamlit_nightly-1.39.1.dev20241102.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.39.1.dev20241031.dist-info → streamlit_nightly-1.39.1.dev20241102.dist-info}/RECORD +48 -47
- streamlit/static/static/js/3224.919d670d.chunk.js +0 -1
- /streamlit/static/static/js/{main.754d974e.js.LICENSE.txt → main.61a89bda.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.39.1.dev20241031.data → streamlit_nightly-1.39.1.dev20241102.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.39.1.dev20241031.dist-info → streamlit_nightly-1.39.1.dev20241102.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.39.1.dev20241031.dist-info → streamlit_nightly-1.39.1.dev20241102.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.39.1.dev20241031.dist-info → streamlit_nightly-1.39.1.dev20241102.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,440 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from __future__ import annotations
|
16
|
+
|
17
|
+
import io
|
18
|
+
import os
|
19
|
+
import re
|
20
|
+
from enum import IntEnum
|
21
|
+
from pathlib import Path
|
22
|
+
from typing import TYPE_CHECKING, Final, Literal, Sequence, Union, cast
|
23
|
+
|
24
|
+
from typing_extensions import TypeAlias
|
25
|
+
|
26
|
+
from streamlit import runtime, url_util
|
27
|
+
from streamlit.errors import StreamlitAPIException
|
28
|
+
from streamlit.runtime import caching
|
29
|
+
from streamlit.type_util import NumpyShape
|
30
|
+
|
31
|
+
if TYPE_CHECKING:
|
32
|
+
from typing import Any
|
33
|
+
|
34
|
+
import numpy.typing as npt
|
35
|
+
from PIL import GifImagePlugin, Image, ImageFile
|
36
|
+
|
37
|
+
from streamlit.proto.Image_pb2 import ImageList as ImageListProto
|
38
|
+
|
39
|
+
PILImage: TypeAlias = Union[
|
40
|
+
"ImageFile.ImageFile", "Image.Image", "GifImagePlugin.GifImageFile"
|
41
|
+
]
|
42
|
+
AtomicImage: TypeAlias = Union[
|
43
|
+
PILImage, "npt.NDArray[Any]", io.BytesIO, str, Path, bytes
|
44
|
+
]
|
45
|
+
|
46
|
+
Channels: TypeAlias = Literal["RGB", "BGR"]
|
47
|
+
ImageFormat: TypeAlias = Literal["JPEG", "PNG", "GIF"]
|
48
|
+
ImageFormatOrAuto: TypeAlias = Literal[ImageFormat, "auto"]
|
49
|
+
ImageOrImageList: TypeAlias = Union[AtomicImage, Sequence[AtomicImage]]
|
50
|
+
|
51
|
+
# This constant is related to the frontend maximum content width specified
|
52
|
+
# in App.jsx main container
|
53
|
+
# 730 is the max width of element-container in the frontend, and 2x is for high
|
54
|
+
# DPI.
|
55
|
+
MAXIMUM_CONTENT_WIDTH: Final[int] = 2 * 730
|
56
|
+
|
57
|
+
|
58
|
+
# @see Image.proto
|
59
|
+
# @see WidthBehavior on the frontend
|
60
|
+
class WidthBehavior(IntEnum):
|
61
|
+
"""
|
62
|
+
Special values that are recognized by the frontend and allow us to change the
|
63
|
+
behavior of the displayed image.
|
64
|
+
"""
|
65
|
+
|
66
|
+
ORIGINAL = -1
|
67
|
+
COLUMN = -2
|
68
|
+
AUTO = -3
|
69
|
+
MIN_IMAGE_OR_CONTAINER = -4
|
70
|
+
MAX_IMAGE_OR_CONTAINER = -5
|
71
|
+
|
72
|
+
|
73
|
+
WidthBehavior.ORIGINAL.__doc__ = """Display the image at its original width"""
|
74
|
+
WidthBehavior.COLUMN.__doc__ = (
|
75
|
+
"""Display the image at the width of the column it's in."""
|
76
|
+
)
|
77
|
+
WidthBehavior.AUTO.__doc__ = """Display the image at its original width, unless it
|
78
|
+
would exceed the width of its column in which case clamp it to
|
79
|
+
its column width"""
|
80
|
+
|
81
|
+
|
82
|
+
def _image_may_have_alpha_channel(image: PILImage) -> bool:
|
83
|
+
return image.mode in ("RGBA", "LA", "P")
|
84
|
+
|
85
|
+
|
86
|
+
def _image_is_gif(image: PILImage) -> bool:
|
87
|
+
return image.format == "GIF"
|
88
|
+
|
89
|
+
|
90
|
+
def _validate_image_format_string(
|
91
|
+
image_data: bytes | PILImage, format: str
|
92
|
+
) -> ImageFormat:
|
93
|
+
"""Return either "JPEG", "PNG", or "GIF", based on the input `format` string.
|
94
|
+
- If `format` is "JPEG" or "JPG" (or any capitalization thereof), return "JPEG"
|
95
|
+
- If `format` is "PNG" (or any capitalization thereof), return "PNG"
|
96
|
+
- For all other strings, return "PNG" if the image has an alpha channel,
|
97
|
+
"GIF" if the image is a GIF, and "JPEG" otherwise.
|
98
|
+
"""
|
99
|
+
format = format.upper()
|
100
|
+
if format in {"JPEG", "PNG"}:
|
101
|
+
return cast(ImageFormat, format)
|
102
|
+
|
103
|
+
# We are forgiving on the spelling of JPEG
|
104
|
+
if format == "JPG":
|
105
|
+
return "JPEG"
|
106
|
+
|
107
|
+
pil_image: PILImage
|
108
|
+
if isinstance(image_data, bytes):
|
109
|
+
from PIL import Image
|
110
|
+
|
111
|
+
pil_image = Image.open(io.BytesIO(image_data))
|
112
|
+
else:
|
113
|
+
pil_image = image_data
|
114
|
+
|
115
|
+
if _image_is_gif(pil_image):
|
116
|
+
return "GIF"
|
117
|
+
|
118
|
+
if _image_may_have_alpha_channel(pil_image):
|
119
|
+
return "PNG"
|
120
|
+
|
121
|
+
return "JPEG"
|
122
|
+
|
123
|
+
|
124
|
+
def _PIL_to_bytes(
|
125
|
+
image: PILImage,
|
126
|
+
format: ImageFormat = "JPEG",
|
127
|
+
quality: int = 100,
|
128
|
+
) -> bytes:
|
129
|
+
"""Convert a PIL image to bytes."""
|
130
|
+
tmp = io.BytesIO()
|
131
|
+
|
132
|
+
# User must have specified JPEG, so we must convert it
|
133
|
+
if format == "JPEG" and _image_may_have_alpha_channel(image):
|
134
|
+
image = image.convert("RGB")
|
135
|
+
|
136
|
+
image.save(tmp, format=format, quality=quality)
|
137
|
+
|
138
|
+
return tmp.getvalue()
|
139
|
+
|
140
|
+
|
141
|
+
def _BytesIO_to_bytes(data: io.BytesIO) -> bytes:
|
142
|
+
data.seek(0)
|
143
|
+
return data.getvalue()
|
144
|
+
|
145
|
+
|
146
|
+
def _np_array_to_bytes(array: npt.NDArray[Any], output_format: str = "JPEG") -> bytes:
|
147
|
+
import numpy as np
|
148
|
+
from PIL import Image
|
149
|
+
|
150
|
+
img = Image.fromarray(array.astype(np.uint8))
|
151
|
+
format = _validate_image_format_string(img, output_format)
|
152
|
+
|
153
|
+
return _PIL_to_bytes(img, format)
|
154
|
+
|
155
|
+
|
156
|
+
def _verify_np_shape(array: npt.NDArray[Any]) -> npt.NDArray[Any]:
|
157
|
+
shape: NumpyShape = array.shape
|
158
|
+
if len(shape) not in (2, 3):
|
159
|
+
raise StreamlitAPIException("Numpy shape has to be of length 2 or 3.")
|
160
|
+
if len(shape) == 3 and shape[-1] not in (1, 3, 4):
|
161
|
+
raise StreamlitAPIException(
|
162
|
+
"Channel can only be 1, 3, or 4 got %d. Shape is %s"
|
163
|
+
% (shape[-1], str(shape))
|
164
|
+
)
|
165
|
+
|
166
|
+
# If there's only one channel, convert is to x, y
|
167
|
+
if len(shape) == 3 and shape[-1] == 1:
|
168
|
+
array = array[:, :, 0]
|
169
|
+
|
170
|
+
return array
|
171
|
+
|
172
|
+
|
173
|
+
def _get_image_format_mimetype(image_format: ImageFormat) -> str:
|
174
|
+
"""Get the mimetype string for the given ImageFormat."""
|
175
|
+
return f"image/{image_format.lower()}"
|
176
|
+
|
177
|
+
|
178
|
+
def _ensure_image_size_and_format(
|
179
|
+
image_data: bytes, width: int, image_format: ImageFormat
|
180
|
+
) -> bytes:
|
181
|
+
"""Resize an image if it exceeds the given width, or if exceeds
|
182
|
+
MAXIMUM_CONTENT_WIDTH. Ensure the image's format corresponds to the given
|
183
|
+
ImageFormat. Return the (possibly resized and reformatted) image bytes.
|
184
|
+
"""
|
185
|
+
from PIL import Image
|
186
|
+
|
187
|
+
pil_image: PILImage = Image.open(io.BytesIO(image_data))
|
188
|
+
actual_width, actual_height = pil_image.size
|
189
|
+
|
190
|
+
if width < 0 and actual_width > MAXIMUM_CONTENT_WIDTH:
|
191
|
+
width = MAXIMUM_CONTENT_WIDTH
|
192
|
+
|
193
|
+
if width > 0 and actual_width > width:
|
194
|
+
# We need to resize the image.
|
195
|
+
new_height = int(1.0 * actual_height * width / actual_width)
|
196
|
+
# pillow reexports Image.Resampling.BILINEAR as Image.BILINEAR for backwards
|
197
|
+
# compatibility reasons, so we use the reexport to support older pillow
|
198
|
+
# versions. The types don't seem to reflect this, though, hence the type: ignore
|
199
|
+
# below.
|
200
|
+
pil_image = pil_image.resize((width, new_height), resample=Image.BILINEAR) # type: ignore[attr-defined]
|
201
|
+
return _PIL_to_bytes(pil_image, format=image_format, quality=90)
|
202
|
+
|
203
|
+
if pil_image.format != image_format:
|
204
|
+
# We need to reformat the image.
|
205
|
+
return _PIL_to_bytes(pil_image, format=image_format, quality=90)
|
206
|
+
|
207
|
+
# No resizing or reformatting necessary - return the original bytes.
|
208
|
+
return image_data
|
209
|
+
|
210
|
+
|
211
|
+
def _clip_image(image: npt.NDArray[Any], clamp: bool) -> npt.NDArray[Any]:
|
212
|
+
import numpy as np
|
213
|
+
|
214
|
+
data = image
|
215
|
+
if issubclass(image.dtype.type, np.floating):
|
216
|
+
if clamp:
|
217
|
+
data = np.clip(image, 0, 1.0)
|
218
|
+
else:
|
219
|
+
if np.amin(image) < 0.0 or np.amax(image) > 1.0:
|
220
|
+
raise RuntimeError("Data is outside [0.0, 1.0] and clamp is not set.")
|
221
|
+
data = data * 255
|
222
|
+
else:
|
223
|
+
if clamp:
|
224
|
+
data = np.clip(image, 0, 255)
|
225
|
+
else:
|
226
|
+
if np.amin(image) < 0 or np.amax(image) > 255:
|
227
|
+
raise RuntimeError("Data is outside [0, 255] and clamp is not set.")
|
228
|
+
return data
|
229
|
+
|
230
|
+
|
231
|
+
def image_to_url(
|
232
|
+
image: AtomicImage,
|
233
|
+
width: int,
|
234
|
+
clamp: bool,
|
235
|
+
channels: Channels,
|
236
|
+
output_format: ImageFormatOrAuto,
|
237
|
+
image_id: str,
|
238
|
+
) -> str:
|
239
|
+
"""Return a URL that an image can be served from.
|
240
|
+
If `image` is already a URL, return it unmodified.
|
241
|
+
Otherwise, add the image to the MediaFileManager and return the URL.
|
242
|
+
(When running in "raw" mode, we won't actually load data into the
|
243
|
+
MediaFileManager, and we'll return an empty URL.)
|
244
|
+
"""
|
245
|
+
import numpy as np
|
246
|
+
from PIL import Image, ImageFile
|
247
|
+
|
248
|
+
image_data: bytes
|
249
|
+
|
250
|
+
# Convert Path to string if necessary
|
251
|
+
if isinstance(image, Path):
|
252
|
+
image = str(image)
|
253
|
+
|
254
|
+
# Strings
|
255
|
+
if isinstance(image, str):
|
256
|
+
if not os.path.isfile(image) and url_util.is_url(
|
257
|
+
image, allowed_schemas=("http", "https", "data")
|
258
|
+
):
|
259
|
+
# If it's a url, return it directly.
|
260
|
+
return image
|
261
|
+
|
262
|
+
if image.endswith(".svg") and os.path.isfile(image):
|
263
|
+
# Unpack local SVG image file to an SVG string
|
264
|
+
with open(image) as textfile:
|
265
|
+
image = textfile.read()
|
266
|
+
|
267
|
+
# Following regex allows svg image files to start either via a "<?xml...>" tag
|
268
|
+
# eventually followed by a "<svg...>" tag or directly starting with a "<svg>" tag
|
269
|
+
if re.search(r"(^\s?(<\?xml[\s\S]*<svg\s)|^\s?<svg\s|^\s?<svg>\s)", image):
|
270
|
+
if "xmlns" not in image:
|
271
|
+
# The xmlns attribute is required for SVGs to render in an img tag.
|
272
|
+
# If it's not present, we add to the first SVG tag:
|
273
|
+
image = image.replace(
|
274
|
+
"<svg", '<svg xmlns="http://www.w3.org/2000/svg" ', 1
|
275
|
+
)
|
276
|
+
# Convert to base64 to prevent issues with encoding:
|
277
|
+
import base64
|
278
|
+
|
279
|
+
image_b64_encoded = base64.b64encode(image.encode("utf-8")).decode("utf-8")
|
280
|
+
# Return SVG as data URI:
|
281
|
+
return f"data:image/svg+xml;base64,{image_b64_encoded}"
|
282
|
+
|
283
|
+
# Otherwise, try to open it as a file.
|
284
|
+
try:
|
285
|
+
with open(image, "rb") as f:
|
286
|
+
image_data = f.read()
|
287
|
+
except Exception:
|
288
|
+
# When we aren't able to open the image file, we still pass the path to
|
289
|
+
# the MediaFileManager - its storage backend may have access to files
|
290
|
+
# that Streamlit does not.
|
291
|
+
import mimetypes
|
292
|
+
|
293
|
+
mimetype, _ = mimetypes.guess_type(image)
|
294
|
+
if mimetype is None:
|
295
|
+
mimetype = "application/octet-stream"
|
296
|
+
|
297
|
+
url = runtime.get_instance().media_file_mgr.add(image, mimetype, image_id)
|
298
|
+
caching.save_media_data(image, mimetype, image_id)
|
299
|
+
return url
|
300
|
+
|
301
|
+
# PIL Images
|
302
|
+
elif isinstance(image, (ImageFile.ImageFile, Image.Image)):
|
303
|
+
format = _validate_image_format_string(image, output_format)
|
304
|
+
image_data = _PIL_to_bytes(image, format)
|
305
|
+
|
306
|
+
# BytesIO
|
307
|
+
# Note: This doesn't support SVG. We could convert to png (cairosvg.svg2png)
|
308
|
+
# or just decode BytesIO to string and handle that way.
|
309
|
+
elif isinstance(image, io.BytesIO):
|
310
|
+
image_data = _BytesIO_to_bytes(image)
|
311
|
+
|
312
|
+
# Numpy Arrays (ie opencv)
|
313
|
+
elif isinstance(image, np.ndarray):
|
314
|
+
image = _clip_image(_verify_np_shape(image), clamp)
|
315
|
+
|
316
|
+
if channels == "BGR":
|
317
|
+
if len(cast(NumpyShape, image.shape)) == 3:
|
318
|
+
image = image[:, :, [2, 1, 0]]
|
319
|
+
else:
|
320
|
+
raise StreamlitAPIException(
|
321
|
+
'When using `channels="BGR"`, the input image should '
|
322
|
+
"have exactly 3 color channels"
|
323
|
+
)
|
324
|
+
|
325
|
+
image_data = _np_array_to_bytes(array=image, output_format=output_format)
|
326
|
+
|
327
|
+
# Raw bytes
|
328
|
+
else:
|
329
|
+
image_data = image
|
330
|
+
|
331
|
+
# Determine the image's format, resize it, and get its mimetype
|
332
|
+
image_format = _validate_image_format_string(image_data, output_format)
|
333
|
+
image_data = _ensure_image_size_and_format(image_data, width, image_format)
|
334
|
+
mimetype = _get_image_format_mimetype(image_format)
|
335
|
+
|
336
|
+
if runtime.exists():
|
337
|
+
url = runtime.get_instance().media_file_mgr.add(image_data, mimetype, image_id)
|
338
|
+
caching.save_media_data(image_data, mimetype, image_id)
|
339
|
+
return url
|
340
|
+
else:
|
341
|
+
# When running in "raw mode", we can't access the MediaFileManager.
|
342
|
+
return ""
|
343
|
+
|
344
|
+
|
345
|
+
def _4d_to_list_3d(array: npt.NDArray[Any]) -> list[npt.NDArray[Any]]:
|
346
|
+
return [array[i, :, :, :] for i in range(0, array.shape[0])]
|
347
|
+
|
348
|
+
|
349
|
+
def marshall_images(
|
350
|
+
coordinates: str,
|
351
|
+
image: ImageOrImageList,
|
352
|
+
caption: str | npt.NDArray[Any] | list[str] | None,
|
353
|
+
width: int | WidthBehavior,
|
354
|
+
proto_imgs: ImageListProto,
|
355
|
+
clamp: bool,
|
356
|
+
channels: Channels = "RGB",
|
357
|
+
output_format: ImageFormatOrAuto = "auto",
|
358
|
+
) -> None:
|
359
|
+
"""Fill an ImageListProto with a list of images and their captions.
|
360
|
+
The images will be resized and reformatted as necessary.
|
361
|
+
Parameters
|
362
|
+
----------
|
363
|
+
coordinates
|
364
|
+
A string indentifying the images' location in the frontend.
|
365
|
+
image
|
366
|
+
The image or images to include in the ImageListProto.
|
367
|
+
caption
|
368
|
+
Image caption. If displaying multiple images, caption should be a
|
369
|
+
list of captions (one for each image).
|
370
|
+
width
|
371
|
+
The desired width of the image or images. This parameter will be
|
372
|
+
passed to the frontend.
|
373
|
+
Positive values set the image width explicitly.
|
374
|
+
Negative values has some special. For details, see: `WidthBehaviour`
|
375
|
+
proto_imgs
|
376
|
+
The ImageListProto to fill in.
|
377
|
+
clamp
|
378
|
+
Clamp image pixel values to a valid range ([0-255] per channel).
|
379
|
+
This is only meaningful for byte array images; the parameter is
|
380
|
+
ignored for image URLs. If this is not set, and an image has an
|
381
|
+
out-of-range value, an error will be thrown.
|
382
|
+
channels
|
383
|
+
If image is an nd.array, this parameter denotes the format used to
|
384
|
+
represent color information. Defaults to 'RGB', meaning
|
385
|
+
`image[:, :, 0]` is the red channel, `image[:, :, 1]` is green, and
|
386
|
+
`image[:, :, 2]` is blue. For images coming from libraries like
|
387
|
+
OpenCV you should set this to 'BGR', instead.
|
388
|
+
output_format
|
389
|
+
This parameter specifies the format to use when transferring the
|
390
|
+
image data. Photos should use the JPEG format for lossy compression
|
391
|
+
while diagrams should use the PNG format for lossless compression.
|
392
|
+
Defaults to 'auto' which identifies the compression type based
|
393
|
+
on the type and format of the image argument.
|
394
|
+
"""
|
395
|
+
import numpy as np
|
396
|
+
|
397
|
+
channels = cast(Channels, channels.upper())
|
398
|
+
|
399
|
+
# Turn single image and caption into one element list.
|
400
|
+
images: Sequence[AtomicImage]
|
401
|
+
if isinstance(image, (list, set, tuple)):
|
402
|
+
images = list(image)
|
403
|
+
elif isinstance(image, np.ndarray) and len(cast(NumpyShape, image.shape)) == 4:
|
404
|
+
images = _4d_to_list_3d(image)
|
405
|
+
else:
|
406
|
+
images = [image] # type: ignore
|
407
|
+
|
408
|
+
if isinstance(caption, list):
|
409
|
+
captions: Sequence[str | None] = caption
|
410
|
+
elif isinstance(caption, str):
|
411
|
+
captions = [caption]
|
412
|
+
elif isinstance(caption, np.ndarray) and len(cast(NumpyShape, caption.shape)) == 1:
|
413
|
+
captions = caption.tolist()
|
414
|
+
elif caption is None:
|
415
|
+
captions = [None] * len(images)
|
416
|
+
else:
|
417
|
+
captions = [str(caption)]
|
418
|
+
|
419
|
+
assert isinstance(
|
420
|
+
captions, list
|
421
|
+
), "If image is a list then caption should be as well"
|
422
|
+
assert len(captions) == len(images), "Cannot pair %d captions with %d images." % (
|
423
|
+
len(captions),
|
424
|
+
len(images),
|
425
|
+
)
|
426
|
+
|
427
|
+
proto_imgs.width = int(width)
|
428
|
+
# Each image in an image list needs to be kept track of at its own coordinates.
|
429
|
+
for coord_suffix, (image, caption) in enumerate(zip(images, captions)):
|
430
|
+
proto_img = proto_imgs.imgs.add()
|
431
|
+
if caption is not None:
|
432
|
+
proto_img.caption = str(caption)
|
433
|
+
|
434
|
+
# We use the index of the image in the input image list to identify this image inside
|
435
|
+
# MediaFileManager. For this, we just add the index to the image's "coordinates".
|
436
|
+
image_id = "%s-%i" % (coordinates, coord_suffix)
|
437
|
+
|
438
|
+
proto_img.url = image_to_url(
|
439
|
+
image, width, clamp, channels, output_format, image_id
|
440
|
+
)
|
streamlit/elements/markdown.py
CHANGED
@@ -52,6 +52,12 @@ class MarkdownMixin:
|
|
52
52
|
For a list of all supported codes,
|
53
53
|
see https://share.streamlit.io/streamlit/emoji-shortcodes.
|
54
54
|
|
55
|
+
* Streamlit logo shortcode. Use ``:streamlit:`` to add a little
|
56
|
+
Streamlit flair to your text.
|
57
|
+
|
58
|
+
* A limited set of typographical symbols. ``"<- -> <-> -- >= <= ~="``
|
59
|
+
becomes "← → ↔ — ≥ ≤ ≈" when parsed as Markdown.
|
60
|
+
|
55
61
|
* Google Material Symbols (rounded style), using the syntax
|
56
62
|
``:material/icon_name:``, where "icon_name" is the name of the
|
57
63
|
icon in snake case. For a complete list of icons, see Google's
|
streamlit/elements/media.py
CHANGED
@@ -43,7 +43,14 @@ if TYPE_CHECKING:
|
|
43
43
|
|
44
44
|
|
45
45
|
MediaData: TypeAlias = Union[
|
46
|
-
str,
|
46
|
+
str,
|
47
|
+
Path,
|
48
|
+
bytes,
|
49
|
+
io.BytesIO,
|
50
|
+
io.RawIOBase,
|
51
|
+
io.BufferedReader,
|
52
|
+
"npt.NDArray[Any]",
|
53
|
+
None,
|
47
54
|
]
|
48
55
|
|
49
56
|
SubtitleData: TypeAlias = Union[
|
@@ -78,8 +85,8 @@ class MediaMixin:
|
|
78
85
|
|
79
86
|
Parameters
|
80
87
|
----------
|
81
|
-
data : str, bytes, BytesIO, numpy.ndarray, or file
|
82
|
-
Raw audio data,
|
88
|
+
data : str, Path, bytes, BytesIO, numpy.ndarray, or file
|
89
|
+
Raw audio data, file path (str or Path object), or a URL pointing to the file to load.
|
83
90
|
Raw data formats must include all necessary file headers to match the file
|
84
91
|
format specified via ``format``.
|
85
92
|
If ``data`` is a numpy array, it must either be a 1D array of the waveform
|
@@ -212,8 +219,8 @@ class MediaMixin:
|
|
212
219
|
|
213
220
|
Parameters
|
214
221
|
----------
|
215
|
-
data : str, bytes, io.BytesIO, numpy.ndarray, or file
|
216
|
-
Raw video data,
|
222
|
+
data : str, Path, bytes, io.BytesIO, numpy.ndarray, or file
|
223
|
+
Raw video data, file path (str or Path object), or URL pointing to a video to load.
|
217
224
|
Includes support for YouTube URLs.
|
218
225
|
Numpy arrays and raw data formats must include all necessary file
|
219
226
|
headers to match specified file format.
|
@@ -421,6 +428,8 @@ def _marshall_av_media(
|
|
421
428
|
if isinstance(data, (str, bytes)):
|
422
429
|
# Pass strings and bytes through unchanged
|
423
430
|
data_or_filename = data
|
431
|
+
elif isinstance(data, Path):
|
432
|
+
data_or_filename = str(data)
|
424
433
|
elif isinstance(data, io.BytesIO):
|
425
434
|
data.seek(0)
|
426
435
|
data_or_filename = data.getvalue()
|
@@ -467,7 +476,7 @@ def marshall_video(
|
|
467
476
|
----------
|
468
477
|
coordinates : str
|
469
478
|
proto : the proto to fill. Must have a string field called "data".
|
470
|
-
data : str, bytes, BytesIO, numpy.ndarray, or file opened with
|
479
|
+
data : str, Path, bytes, BytesIO, numpy.ndarray, or file opened with
|
471
480
|
io.open().
|
472
481
|
Raw video data or a string with a URL pointing to the video
|
473
482
|
to load. Includes support for YouTube URLs.
|
@@ -520,6 +529,9 @@ def marshall_video(
|
|
520
529
|
# "type" distinguishes between YouTube and non-YouTube links
|
521
530
|
proto.type = VideoProto.Type.NATIVE
|
522
531
|
|
532
|
+
if isinstance(data, Path):
|
533
|
+
data = str(data) # Convert Path to string
|
534
|
+
|
523
535
|
if isinstance(data, str) and url_util.is_url(
|
524
536
|
data, allowed_schemas=("http", "https", "data")
|
525
537
|
):
|
@@ -532,7 +544,6 @@ def marshall_video(
|
|
532
544
|
)
|
533
545
|
else:
|
534
546
|
proto.url = data
|
535
|
-
|
536
547
|
else:
|
537
548
|
_marshall_av_media(coordinates, proto, data, mimetype)
|
538
549
|
|
@@ -711,7 +722,7 @@ def marshall_audio(
|
|
711
722
|
----------
|
712
723
|
coordinates : str
|
713
724
|
proto : The proto to fill. Must have a string field called "url".
|
714
|
-
data : str, bytes, BytesIO, numpy.ndarray, or file opened with
|
725
|
+
data : str, Path, bytes, BytesIO, numpy.ndarray, or file opened with
|
715
726
|
io.open()
|
716
727
|
Raw audio data or a string with a URL pointing to the file to load.
|
717
728
|
If passing the raw data, this must include headers and any other bytes
|
@@ -740,11 +751,13 @@ def marshall_audio(
|
|
740
751
|
proto.end_time = end_time
|
741
752
|
proto.loop = loop
|
742
753
|
|
754
|
+
if isinstance(data, Path):
|
755
|
+
data = str(data) # Convert Path to string
|
756
|
+
|
743
757
|
if isinstance(data, str) and url_util.is_url(
|
744
758
|
data, allowed_schemas=("http", "https", "data")
|
745
759
|
):
|
746
760
|
proto.url = data
|
747
|
-
|
748
761
|
else:
|
749
762
|
data = _maybe_convert_to_wav_bytes(data, sample_rate)
|
750
763
|
_marshall_av_media(coordinates, proto, data, mimetype)
|
streamlit/elements/metric.py
CHANGED
@@ -70,7 +70,8 @@ class MetricMixin:
|
|
70
70
|
label : str
|
71
71
|
The header or title for the metric. The label can optionally
|
72
72
|
contain GitHub-flavored Markdown of the following types: Bold, Italics,
|
73
|
-
Strikethroughs, Inline Code, and
|
73
|
+
Strikethroughs, Inline Code, Links, and Images. Images display like
|
74
|
+
icons, with a max height equal to the font height.
|
74
75
|
|
75
76
|
Unsupported Markdown elements are unwrapped so only their children
|
76
77
|
(text contents) render. Display unsupported elements as literal
|
@@ -102,12 +103,14 @@ class MetricMixin:
|
|
102
103
|
|
103
104
|
help : str
|
104
105
|
An optional tooltip that gets displayed next to the metric label.
|
106
|
+
Streamlit only displays the tooltip when
|
107
|
+
``label_visibility="visible"``.
|
105
108
|
|
106
109
|
label_visibility : "visible", "hidden", or "collapsed"
|
107
|
-
The visibility of the label.
|
108
|
-
is
|
109
|
-
|
110
|
-
"
|
110
|
+
The visibility of the label. The default is ``"visible"``. If this
|
111
|
+
is ``"hidden"``, Streamlit displays an empty spacer instead of the
|
112
|
+
label, which can help keep the widget alligned with other widgets.
|
113
|
+
If this is ``"collapsed"``, Streamlit displays no label or spacer.
|
111
114
|
|
112
115
|
Example
|
113
116
|
-------
|
streamlit/elements/progress.py
CHANGED
@@ -105,7 +105,8 @@ class ProgressMixin:
|
|
105
105
|
text : str or None
|
106
106
|
A message to display above the progress bar. The text can optionally
|
107
107
|
contain GitHub-flavored Markdown of the following types: Bold, Italics,
|
108
|
-
Strikethroughs, Inline Code, and
|
108
|
+
Strikethroughs, Inline Code, Links, and Images. Images display like
|
109
|
+
icons, with a max height equal to the font height.
|
109
110
|
|
110
111
|
Unsupported Markdown elements are unwrapped so only their children
|
111
112
|
(text contents) render. Display unsupported elements as literal
|
streamlit/elements/pyplot.py
CHANGED
@@ -19,8 +19,8 @@ from __future__ import annotations
|
|
19
19
|
import io
|
20
20
|
from typing import TYPE_CHECKING, Any, cast
|
21
21
|
|
22
|
-
import streamlit.elements.image as image_utils
|
23
22
|
from streamlit.deprecation_util import show_deprecation_warning
|
23
|
+
from streamlit.elements.lib.image_utils import WidthBehavior, marshall_images
|
24
24
|
from streamlit.proto.Image_pb2 import ImageList as ImageListProto
|
25
25
|
from streamlit.runtime.metrics_util import gather_metrics
|
26
26
|
|
@@ -172,11 +172,9 @@ def marshall(
|
|
172
172
|
image = io.BytesIO()
|
173
173
|
fig.savefig(image, **kwargs)
|
174
174
|
image_width = (
|
175
|
-
|
176
|
-
if use_container_width
|
177
|
-
else image_utils.WidthBehaviour.ORIGINAL
|
175
|
+
WidthBehavior.COLUMN if use_container_width else WidthBehavior.ORIGINAL
|
178
176
|
)
|
179
|
-
|
177
|
+
marshall_images(
|
180
178
|
coordinates=coordinates,
|
181
179
|
image=image,
|
182
180
|
caption=None,
|
streamlit/elements/text.py
CHANGED
@@ -52,7 +52,7 @@ class TextMixin:
|
|
52
52
|
-------
|
53
53
|
>>> import streamlit as st
|
54
54
|
>>>
|
55
|
-
>>> st.text("This is text
|
55
|
+
>>> st.text("This is text\\n[and more text](that's not a Markdown link).")
|
56
56
|
|
57
57
|
.. output ::
|
58
58
|
https://doc-text.streamlit.app/
|
@@ -107,8 +107,9 @@ class AudioInputMixin:
|
|
107
107
|
label : str
|
108
108
|
A short label explaining to the user what this widget is used for.
|
109
109
|
The label can optionally contain GitHub-flavored Markdown of the
|
110
|
-
following types: Bold, Italics, Strikethroughs, Inline Code,
|
111
|
-
|
110
|
+
following types: Bold, Italics, Strikethroughs, Inline Code, Links,
|
111
|
+
and Images. Images display like icons, with a max height equal to
|
112
|
+
the font height.
|
112
113
|
|
113
114
|
Unsupported Markdown elements are unwrapped so only their children
|
114
115
|
(text contents) render. Display unsupported elements as literal
|
@@ -118,9 +119,9 @@ class AudioInputMixin:
|
|
118
119
|
See the ``body`` parameter of |st.markdown|_ for additional,
|
119
120
|
supported Markdown directives.
|
120
121
|
|
121
|
-
For accessibility reasons, you should never set an empty label
|
122
|
-
|
123
|
-
empty labels by raising an exception.
|
122
|
+
For accessibility reasons, you should never set an empty label, but
|
123
|
+
you can hide it with ``label_visibility`` if needed. In the future,
|
124
|
+
we may disallow empty labels by raising an exception.
|
124
125
|
|
125
126
|
.. |st.markdown| replace:: ``st.markdown``
|
126
127
|
.. _st.markdown: https://docs.streamlit.io/develop/api-reference/text/st.markdown
|
@@ -144,14 +145,14 @@ class AudioInputMixin:
|
|
144
145
|
An optional dict of kwargs to pass to the callback.
|
145
146
|
|
146
147
|
disabled : bool
|
147
|
-
An optional boolean
|
148
|
-
True
|
148
|
+
An optional boolean that disables the audio input if set to
|
149
|
+
``True``. Default is ``False``.
|
149
150
|
|
150
151
|
label_visibility : "visible", "hidden", or "collapsed"
|
151
|
-
The visibility of the label.
|
152
|
-
is
|
153
|
-
|
154
|
-
"
|
152
|
+
The visibility of the label. The default is ``"visible"``. If this
|
153
|
+
is ``"hidden"``, Streamlit displays an empty spacer instead of the
|
154
|
+
label, which can help keep the widget alligned with other widgets.
|
155
|
+
If this is ``"collapsed"``, Streamlit displays no label or spacer.
|
155
156
|
|
156
157
|
Returns
|
157
158
|
-------
|