figpack 0.2.27__py3-none-any.whl → 0.2.40__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.
- figpack/__init__.py +1 -1
- figpack/cli.py +214 -2
- figpack/core/_bundle_utils.py +12 -8
- figpack/core/_file_handler.py +4 -1
- figpack/core/_save_figure.py +12 -8
- figpack/core/_server_manager.py +106 -3
- figpack/core/_show_view.py +1 -1
- figpack/core/_upload_bundle.py +63 -51
- figpack/core/_view_figure.py +15 -10
- figpack/core/_zarr_consolidate.py +185 -0
- figpack/core/extension_view.py +8 -4
- figpack/core/figpack_extension.py +1 -1
- figpack/core/figpack_view.py +29 -13
- figpack/core/zarr.py +2 -2
- figpack/figpack-figure-dist/assets/{index-DnHZdWys.js → index-ST_DU17U.js} +39 -39
- figpack/figpack-figure-dist/index.html +2 -2
- figpack/views/Box.py +2 -2
- figpack/views/CaptionedView.py +64 -0
- figpack/views/Iframe.py +43 -0
- figpack/views/Image.py +1 -2
- figpack/views/Markdown.py +7 -3
- figpack/views/PlotlyExtension/PlotlyExtension.py +12 -12
- figpack/views/Spectrogram.py +2 -0
- figpack/views/TimeseriesGraph.py +84 -15
- figpack/views/__init__.py +2 -0
- {figpack-0.2.27.dist-info → figpack-0.2.40.dist-info}/METADATA +22 -1
- figpack-0.2.40.dist-info/RECORD +50 -0
- figpack-0.2.27.dist-info/RECORD +0 -47
- {figpack-0.2.27.dist-info → figpack-0.2.40.dist-info}/WHEEL +0 -0
- {figpack-0.2.27.dist-info → figpack-0.2.40.dist-info}/entry_points.txt +0 -0
- {figpack-0.2.27.dist-info → figpack-0.2.40.dist-info}/licenses/LICENSE +0 -0
- {figpack-0.2.27.dist-info → figpack-0.2.40.dist-info}/top_level.txt +0 -0
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
<title>figpack figure</title>
|
|
8
8
|
<script>
|
|
9
9
|
// Allow script injection from trusted domains (see src/main.tsx)
|
|
10
|
-
window.script_injection_allowed_domains = ['https://manage.figpack.org', 'https://figpack.org'];
|
|
10
|
+
window.script_injection_allowed_domains = ['https://manage.figpack.org', 'https://figpack.org', "https://documents.figpack.org"];
|
|
11
11
|
</script>
|
|
12
|
-
<script type="module" crossorigin src="./assets/index-
|
|
12
|
+
<script type="module" crossorigin src="./assets/index-ST_DU17U.js"></script>
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./assets/index-V5m_wCvw.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
figpack/views/Box.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Box view for figpack - a layout container that handles other views
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import List, Literal, Optional
|
|
6
6
|
|
|
7
7
|
from ..core.figpack_view import FigpackView
|
|
8
8
|
from ..core.zarr import Group
|
|
@@ -21,7 +21,7 @@ class Box(FigpackView):
|
|
|
21
21
|
show_titles: bool = True,
|
|
22
22
|
items: List[LayoutItem],
|
|
23
23
|
title: Optional[str] = None,
|
|
24
|
-
):
|
|
24
|
+
) -> None:
|
|
25
25
|
"""
|
|
26
26
|
Initialize a Box layout view
|
|
27
27
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CaptionedView for figpack - displays a view with a caption below it
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from ..core.figpack_view import FigpackView
|
|
9
|
+
from ..core.zarr import Group
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CaptionedView(FigpackView):
|
|
13
|
+
"""
|
|
14
|
+
A view that displays a child view with a text caption below it.
|
|
15
|
+
The caption height dynamically adjusts based on text length and container width.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*,
|
|
21
|
+
view: FigpackView,
|
|
22
|
+
caption: str,
|
|
23
|
+
font_size: Optional[int] = None,
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Initialize a CaptionedView
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
view: The child view to display above the caption
|
|
30
|
+
caption: The text caption to display below the view
|
|
31
|
+
font_size: Optional font size for the caption text (default: 14)
|
|
32
|
+
"""
|
|
33
|
+
self.view = view
|
|
34
|
+
self.caption = caption
|
|
35
|
+
self.font_size = font_size if font_size is not None else 14
|
|
36
|
+
|
|
37
|
+
def write_to_zarr_group(self, group: Group) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Write the CaptionedView data to a Zarr group
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
group: Zarr group to write data into
|
|
43
|
+
"""
|
|
44
|
+
# Set the view type
|
|
45
|
+
group.attrs["view_type"] = "CaptionedView"
|
|
46
|
+
|
|
47
|
+
# Set caption properties
|
|
48
|
+
group.attrs["font_size"] = self.font_size
|
|
49
|
+
|
|
50
|
+
# Convert caption string to numpy array of bytes
|
|
51
|
+
caption_bytes = self.caption.encode("utf-8")
|
|
52
|
+
caption_array = np.frombuffer(caption_bytes, dtype=np.uint8)
|
|
53
|
+
|
|
54
|
+
# Store the caption as a zarr array
|
|
55
|
+
group.create_dataset("caption_data", data=caption_array)
|
|
56
|
+
|
|
57
|
+
# Store caption size in attrs
|
|
58
|
+
group.attrs["caption_size"] = len(caption_bytes)
|
|
59
|
+
|
|
60
|
+
# Create a subgroup for the child view
|
|
61
|
+
child_group = group.create_group("child_view")
|
|
62
|
+
|
|
63
|
+
# Recursively write the child view to the subgroup
|
|
64
|
+
self.view.write_to_zarr_group(child_group)
|
figpack/views/Iframe.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Iframe view for figpack - displays content in an iframe
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ..core.figpack_view import FigpackView
|
|
8
|
+
from ..core.zarr import Group
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Iframe(FigpackView):
|
|
12
|
+
"""
|
|
13
|
+
An iframe visualization component for displaying web content
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, url: str):
|
|
17
|
+
"""
|
|
18
|
+
Initialize an Iframe view
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
url: The URL to display in the iframe
|
|
22
|
+
"""
|
|
23
|
+
self.url = url
|
|
24
|
+
|
|
25
|
+
def write_to_zarr_group(self, group: Group) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Write the iframe data to a Zarr group
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
group: Zarr group to write data into
|
|
31
|
+
"""
|
|
32
|
+
# Set the view type
|
|
33
|
+
group.attrs["view_type"] = "Iframe"
|
|
34
|
+
|
|
35
|
+
# Convert URL to numpy array of bytes
|
|
36
|
+
url_bytes = self.url.encode("utf-8")
|
|
37
|
+
url_array = np.frombuffer(url_bytes, dtype=np.uint8)
|
|
38
|
+
|
|
39
|
+
# Store the URL as a zarr array
|
|
40
|
+
group.create_dataset("url_data", data=url_array)
|
|
41
|
+
|
|
42
|
+
# Store URL size in attrs
|
|
43
|
+
group.attrs["data_size"] = len(url_bytes)
|
figpack/views/Image.py
CHANGED
|
@@ -5,7 +5,6 @@ Image view for figpack - displays PNG and JPG images
|
|
|
5
5
|
from typing import Union
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
-
import zarr
|
|
9
8
|
|
|
10
9
|
from ..core.figpack_view import FigpackView
|
|
11
10
|
from ..core.zarr import Group
|
|
@@ -16,7 +15,7 @@ class Image(FigpackView):
|
|
|
16
15
|
An image visualization component for PNG and JPG files
|
|
17
16
|
"""
|
|
18
17
|
|
|
19
|
-
def __init__(self, image_path_or_data: Union[str, bytes]):
|
|
18
|
+
def __init__(self, image_path_or_data: Union[str, bytes]) -> None:
|
|
20
19
|
"""
|
|
21
20
|
Initialize an Image view
|
|
22
21
|
|
figpack/views/Markdown.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Markdown view for figpack - displays markdown content
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from typing import Optional
|
|
5
6
|
import numpy as np
|
|
6
|
-
import zarr
|
|
7
7
|
|
|
8
8
|
from ..core.figpack_view import FigpackView
|
|
9
9
|
from ..core.zarr import Group
|
|
@@ -14,7 +14,7 @@ class Markdown(FigpackView):
|
|
|
14
14
|
A markdown content visualization component
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
def __init__(self, content: str):
|
|
17
|
+
def __init__(self, content: str, *, font_size: Optional[int] = None):
|
|
18
18
|
"""
|
|
19
19
|
Initialize a Markdown view
|
|
20
20
|
|
|
@@ -22,6 +22,7 @@ class Markdown(FigpackView):
|
|
|
22
22
|
content: The markdown content to display
|
|
23
23
|
"""
|
|
24
24
|
self.content = content
|
|
25
|
+
self.font_size = font_size
|
|
25
26
|
|
|
26
27
|
def write_to_zarr_group(self, group: Group) -> None:
|
|
27
28
|
"""
|
|
@@ -33,12 +34,15 @@ class Markdown(FigpackView):
|
|
|
33
34
|
# Set the view type
|
|
34
35
|
group.attrs["view_type"] = "Markdown"
|
|
35
36
|
|
|
37
|
+
if self.font_size is not None:
|
|
38
|
+
group.attrs["font_size"] = self.font_size
|
|
39
|
+
|
|
36
40
|
# Convert string content to numpy array of bytes
|
|
37
41
|
content_bytes = self.content.encode("utf-8")
|
|
38
42
|
content_array = np.frombuffer(content_bytes, dtype=np.uint8)
|
|
39
43
|
|
|
40
44
|
# Store the markdown content as a zarr array
|
|
41
|
-
group.create_dataset("content_data", data=content_array
|
|
45
|
+
group.create_dataset("content_data", data=content_array)
|
|
42
46
|
|
|
43
47
|
# Store content size in attrs
|
|
44
48
|
group.attrs["data_size"] = len(content_bytes)
|
|
@@ -55,15 +55,15 @@ class PlotlyFigure(figpack.ExtensionView):
|
|
|
55
55
|
class CustomJSONEncoder(json.JSONEncoder):
|
|
56
56
|
"""Custom JSON encoder that handles numpy arrays and datetime objects"""
|
|
57
57
|
|
|
58
|
-
def default(self,
|
|
59
|
-
if isinstance(
|
|
60
|
-
return
|
|
61
|
-
elif isinstance(
|
|
62
|
-
return
|
|
63
|
-
elif isinstance(
|
|
64
|
-
return
|
|
65
|
-
elif isinstance(
|
|
66
|
-
return str(
|
|
67
|
-
elif hasattr(
|
|
68
|
-
return
|
|
69
|
-
return super().default(
|
|
58
|
+
def default(self, o):
|
|
59
|
+
if isinstance(o, np.ndarray):
|
|
60
|
+
return o.tolist()
|
|
61
|
+
elif isinstance(o, (np.integer, np.floating)):
|
|
62
|
+
return o.item()
|
|
63
|
+
elif isinstance(o, (datetime, date)):
|
|
64
|
+
return o.isoformat()
|
|
65
|
+
elif isinstance(o, np.datetime64):
|
|
66
|
+
return str(o)
|
|
67
|
+
elif hasattr(o, "isoformat"): # Handle other datetime-like objects
|
|
68
|
+
return o.isoformat()
|
|
69
|
+
return super().default(o)
|
figpack/views/Spectrogram.py
CHANGED
|
@@ -62,12 +62,14 @@ class Spectrogram(FigpackView):
|
|
|
62
62
|
|
|
63
63
|
# Store frequency information
|
|
64
64
|
if uniform_specified:
|
|
65
|
+
assert frequency_delta_hz is not None, "Frequency delta must be provided"
|
|
65
66
|
assert frequency_delta_hz > 0, "Frequency delta must be positive"
|
|
66
67
|
self.uniform_frequencies = True
|
|
67
68
|
self.frequency_min_hz = frequency_min_hz
|
|
68
69
|
self.frequency_delta_hz = frequency_delta_hz
|
|
69
70
|
self.frequencies = None
|
|
70
71
|
else:
|
|
72
|
+
assert frequencies is not None, "Frequencies array must be provided"
|
|
71
73
|
assert (
|
|
72
74
|
len(frequencies) == data.shape[1]
|
|
73
75
|
), f"Number of frequencies ({len(frequencies)}) must match data frequency dimension ({data.shape[1]})"
|
figpack/views/TimeseriesGraph.py
CHANGED
|
@@ -3,7 +3,7 @@ Views module for figpack - contains visualization components
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
|
-
from typing import Any, Dict, List, Optional, Union
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
@@ -26,7 +26,7 @@ class TimeseriesGraph(FigpackView):
|
|
|
26
26
|
hide_nav_toolbar: bool = False,
|
|
27
27
|
hide_time_axis_labels: bool = False,
|
|
28
28
|
y_label: str = "",
|
|
29
|
-
):
|
|
29
|
+
) -> None:
|
|
30
30
|
"""
|
|
31
31
|
Initialize a TimeseriesGraph
|
|
32
32
|
|
|
@@ -54,8 +54,8 @@ class TimeseriesGraph(FigpackView):
|
|
|
54
54
|
self,
|
|
55
55
|
*,
|
|
56
56
|
name: str,
|
|
57
|
-
t: np.ndarray,
|
|
58
|
-
y: np.ndarray,
|
|
57
|
+
t: Union[np.ndarray, List[float]],
|
|
58
|
+
y: Union[np.ndarray, List[float]],
|
|
59
59
|
color: str = "blue",
|
|
60
60
|
width: float = 1.0,
|
|
61
61
|
dash: Optional[List[float]] = None,
|
|
@@ -71,6 +71,13 @@ class TimeseriesGraph(FigpackView):
|
|
|
71
71
|
width: Line width
|
|
72
72
|
dash: Dash pattern as [dash_length, gap_length]
|
|
73
73
|
"""
|
|
74
|
+
if isinstance(t, list):
|
|
75
|
+
t = np.array(t)
|
|
76
|
+
if isinstance(y, list):
|
|
77
|
+
y = np.array(y)
|
|
78
|
+
assert t.ndim == 1, "Time array must be 1-dimensional"
|
|
79
|
+
assert y.ndim == 1, "Y array must be 1-dimensional"
|
|
80
|
+
assert len(t) == len(y), "Time and Y arrays must have the same length"
|
|
74
81
|
self._series.append(
|
|
75
82
|
TGLineSeries(name=name, t=t, y=y, color=color, width=width, dash=dash)
|
|
76
83
|
)
|
|
@@ -96,6 +103,13 @@ class TimeseriesGraph(FigpackView):
|
|
|
96
103
|
radius: Marker radius
|
|
97
104
|
shape: Marker shape ("circle", "square", etc.)
|
|
98
105
|
"""
|
|
106
|
+
if isinstance(t, list):
|
|
107
|
+
t = np.array(t)
|
|
108
|
+
if isinstance(y, list):
|
|
109
|
+
y = np.array(y)
|
|
110
|
+
assert t.ndim == 1, "Time array must be 1-dimensional"
|
|
111
|
+
assert y.ndim == 1, "Y array must be 1-dimensional"
|
|
112
|
+
assert len(t) == len(y), "Time and Y arrays must have the same length"
|
|
99
113
|
self._series.append(
|
|
100
114
|
TGMarkerSeries(name=name, t=t, y=y, color=color, radius=radius, shape=shape)
|
|
101
115
|
)
|
|
@@ -119,6 +133,18 @@ class TimeseriesGraph(FigpackView):
|
|
|
119
133
|
color: Fill color
|
|
120
134
|
alpha: Transparency (0-1)
|
|
121
135
|
"""
|
|
136
|
+
if isinstance(t_start, list):
|
|
137
|
+
t_start = np.array(t_start)
|
|
138
|
+
if isinstance(t_end, list):
|
|
139
|
+
t_end = np.array(t_end)
|
|
140
|
+
assert t_start.ndim == 1, "Start time array must be 1-dimensional"
|
|
141
|
+
assert t_end.ndim == 1, "End time array must be 1-dimensional"
|
|
142
|
+
assert len(t_start) == len(
|
|
143
|
+
t_end
|
|
144
|
+
), "Start and end time arrays must have the same length"
|
|
145
|
+
assert np.all(
|
|
146
|
+
t_start <= t_end
|
|
147
|
+
), "Start times must be less than or equal to end times"
|
|
122
148
|
self._series.append(
|
|
123
149
|
TGIntervalSeries(
|
|
124
150
|
name=name, t_start=t_start, t_end=t_end, color=color, alpha=alpha
|
|
@@ -137,6 +163,7 @@ class TimeseriesGraph(FigpackView):
|
|
|
137
163
|
width: float = 1.0,
|
|
138
164
|
channel_spacing: Optional[float] = None,
|
|
139
165
|
auto_channel_spacing: Optional[float] = None,
|
|
166
|
+
timestamps_for_inserting_nans: Optional[np.ndarray] = None,
|
|
140
167
|
) -> None:
|
|
141
168
|
"""
|
|
142
169
|
Add a uniform timeseries to the graph with optional multi-channel support
|
|
@@ -151,7 +178,10 @@ class TimeseriesGraph(FigpackView):
|
|
|
151
178
|
width: Line width
|
|
152
179
|
channel_spacing: Vertical spacing between channels
|
|
153
180
|
auto_channel_spacing: sets channel spacing to this multiple of the estimated RMS noise level
|
|
181
|
+
timestamps_for_inserting_nans: Optional array of timestamps used to determine where to insert NaNs in the data
|
|
154
182
|
"""
|
|
183
|
+
if isinstance(data, list):
|
|
184
|
+
data = np.array(data)
|
|
155
185
|
self._series.append(
|
|
156
186
|
TGUniformSeries(
|
|
157
187
|
name=name,
|
|
@@ -163,6 +193,7 @@ class TimeseriesGraph(FigpackView):
|
|
|
163
193
|
width=width,
|
|
164
194
|
channel_spacing=channel_spacing,
|
|
165
195
|
auto_channel_spacing=auto_channel_spacing,
|
|
196
|
+
timestamps_for_inserting_nans=timestamps_for_inserting_nans,
|
|
166
197
|
)
|
|
167
198
|
)
|
|
168
199
|
|
|
@@ -210,7 +241,7 @@ class TGLineSeries:
|
|
|
210
241
|
color: str,
|
|
211
242
|
width: float,
|
|
212
243
|
dash: Optional[List[float]],
|
|
213
|
-
):
|
|
244
|
+
) -> None:
|
|
214
245
|
assert t.ndim == 1, "Time array must be 1-dimensional"
|
|
215
246
|
assert y.ndim == 1, "Y array must be 1-dimensional"
|
|
216
247
|
assert len(t) == len(y), "Time and Y arrays must have the same length"
|
|
@@ -243,7 +274,7 @@ class TGMarkerSeries:
|
|
|
243
274
|
color: str,
|
|
244
275
|
radius: float,
|
|
245
276
|
shape: str,
|
|
246
|
-
):
|
|
277
|
+
) -> None:
|
|
247
278
|
assert t.ndim == 1, "Time array must be 1-dimensional"
|
|
248
279
|
assert y.ndim == 1, "Y array must be 1-dimensional"
|
|
249
280
|
assert len(t) == len(y), "Time and Y arrays must have the same length"
|
|
@@ -278,7 +309,7 @@ class TGIntervalSeries:
|
|
|
278
309
|
t_end: np.ndarray,
|
|
279
310
|
color: str,
|
|
280
311
|
alpha: float,
|
|
281
|
-
):
|
|
312
|
+
) -> None:
|
|
282
313
|
assert t_start.ndim == 1, "Start time array must be 1-dimensional"
|
|
283
314
|
assert t_end.ndim == 1, "End time array must be 1-dimensional"
|
|
284
315
|
assert len(t_start) == len(
|
|
@@ -320,7 +351,8 @@ class TGUniformSeries:
|
|
|
320
351
|
width: float = 1.0,
|
|
321
352
|
channel_spacing: Optional[float] = None,
|
|
322
353
|
auto_channel_spacing: Optional[float] = None,
|
|
323
|
-
|
|
354
|
+
timestamps_for_inserting_nans: Optional[np.ndarray] = None,
|
|
355
|
+
) -> None:
|
|
324
356
|
assert sampling_frequency_hz > 0, "Sampling frequency must be positive"
|
|
325
357
|
|
|
326
358
|
# Handle both 1D and 2D data
|
|
@@ -340,16 +372,31 @@ class TGUniformSeries:
|
|
|
340
372
|
self.sampling_frequency_hz = sampling_frequency_hz
|
|
341
373
|
self.data = data.astype(np.float32) # Ensure float32 for efficiency
|
|
342
374
|
|
|
375
|
+
if timestamps_for_inserting_nans is not None:
|
|
376
|
+
self.data = insert_nans_based_on_timestamps(
|
|
377
|
+
self.data,
|
|
378
|
+
start_time_sec=start_time_sec,
|
|
379
|
+
sampling_frequency_hz=sampling_frequency_hz,
|
|
380
|
+
timestamps=timestamps_for_inserting_nans,
|
|
381
|
+
)
|
|
382
|
+
|
|
343
383
|
if auto_channel_spacing is not None:
|
|
344
384
|
if channel_spacing is not None:
|
|
345
385
|
raise ValueError(
|
|
346
386
|
"Specify either channel_spacing or auto_channel_spacing, not both."
|
|
347
387
|
)
|
|
348
388
|
# Estimate RMS noise level across all channels using median absolute deviation
|
|
349
|
-
|
|
389
|
+
# Use nanmedian to handle NaN values properly
|
|
390
|
+
mad = np.nanmedian(
|
|
391
|
+
np.abs(self.data - np.nanmedian(self.data, axis=0)), axis=0
|
|
392
|
+
)
|
|
350
393
|
rms_estimate = mad / 0.6745 # Convert MAD to RMS estimate
|
|
351
|
-
channel_spacing = auto_channel_spacing * np.
|
|
352
|
-
if
|
|
394
|
+
channel_spacing = auto_channel_spacing * np.nanmedian(rms_estimate)
|
|
395
|
+
if (
|
|
396
|
+
channel_spacing is None
|
|
397
|
+
or (channel_spacing <= 0)
|
|
398
|
+
or np.isnan(channel_spacing)
|
|
399
|
+
):
|
|
353
400
|
channel_spacing = 1.0 # Fallback to default spacing if estimate fails
|
|
354
401
|
self.channel_spacing = channel_spacing
|
|
355
402
|
|
|
@@ -394,7 +441,7 @@ class TGUniformSeries:
|
|
|
394
441
|
# Prepare downsampled arrays for efficient rendering
|
|
395
442
|
self.downsampled_data = self._compute_downsampled_data()
|
|
396
443
|
|
|
397
|
-
def _compute_downsampled_data(self) ->
|
|
444
|
+
def _compute_downsampled_data(self) -> Dict[int, np.ndarray]:
|
|
398
445
|
"""
|
|
399
446
|
Compute downsampled arrays at power-of-4 factors using a vectorized
|
|
400
447
|
min/max pyramid with NaN padding for partial bins.
|
|
@@ -469,8 +516,8 @@ class TGUniformSeries:
|
|
|
469
516
|
return downsampled
|
|
470
517
|
|
|
471
518
|
def _calculate_optimal_chunk_size(
|
|
472
|
-
self, shape:
|
|
473
|
-
) ->
|
|
519
|
+
self, shape: Tuple[int, ...], target_size_mb: float = 5.0
|
|
520
|
+
) -> Tuple[int, ...]:
|
|
474
521
|
"""
|
|
475
522
|
Calculate optimal chunk size for Zarr storage targeting ~5MB per chunk
|
|
476
523
|
|
|
@@ -534,7 +581,7 @@ class TGUniformSeries:
|
|
|
534
581
|
group.attrs["n_channels"] = n_channels
|
|
535
582
|
|
|
536
583
|
if self.channel_spacing is not None:
|
|
537
|
-
group.attrs["channel_spacing"] = self.channel_spacing
|
|
584
|
+
group.attrs["channel_spacing"] = float(self.channel_spacing)
|
|
538
585
|
|
|
539
586
|
# Store original data with optimal chunking
|
|
540
587
|
original_chunks = self._calculate_optimal_chunk_size(self.data.shape)
|
|
@@ -559,3 +606,25 @@ class TGUniformSeries:
|
|
|
559
606
|
data=downsampled_array,
|
|
560
607
|
chunks=ds_chunks,
|
|
561
608
|
)
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def insert_nans_based_on_timestamps(
|
|
612
|
+
x: np.ndarray,
|
|
613
|
+
*,
|
|
614
|
+
start_time_sec: float,
|
|
615
|
+
sampling_frequency_hz: float,
|
|
616
|
+
timestamps: np.ndarray,
|
|
617
|
+
) -> np.ndarray:
|
|
618
|
+
end_timestamps = timestamps[-1]
|
|
619
|
+
ret_length = int((end_timestamps - start_time_sec) * sampling_frequency_hz) + 1
|
|
620
|
+
|
|
621
|
+
# Handle both 1D and 2D (multi-channel) data
|
|
622
|
+
if x.ndim == 1:
|
|
623
|
+
ret = np.nan * np.ones((ret_length,), dtype=x.dtype)
|
|
624
|
+
else: # x.ndim == 2
|
|
625
|
+
n_channels = x.shape[1]
|
|
626
|
+
ret = np.nan * np.ones((ret_length, n_channels), dtype=x.dtype)
|
|
627
|
+
|
|
628
|
+
indices = ((timestamps - start_time_sec) * sampling_frequency_hz).astype(int)
|
|
629
|
+
ret[indices] = x
|
|
630
|
+
return ret
|
figpack/views/__init__.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from .Box import Box
|
|
2
|
+
from .CaptionedView import CaptionedView
|
|
2
3
|
from .DataFrame import DataFrame
|
|
3
4
|
from .Gallery import Gallery
|
|
4
5
|
from .GalleryItem import GalleryItem
|
|
6
|
+
from .Iframe import Iframe
|
|
5
7
|
from .Image import Image
|
|
6
8
|
from .LayoutItem import LayoutItem
|
|
7
9
|
from .Markdown import Markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: figpack
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.40
|
|
4
4
|
Summary: A Python package for creating shareable, interactive visualizations in the browser
|
|
5
5
|
Author-email: Jeremy Magland <jmagland@flatironinstitute.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -114,6 +114,27 @@ graph.show(open_in_browser=True, title="Quick Start Example")
|
|
|
114
114
|
|
|
115
115
|
Apache-2.0
|
|
116
116
|
|
|
117
|
+
## Citation
|
|
118
|
+
|
|
119
|
+
If you use figpack in your research, please cite it:
|
|
120
|
+
|
|
121
|
+
[](https://doi.org/10.5281/zenodo.17419621)
|
|
122
|
+
|
|
123
|
+
```bibtex
|
|
124
|
+
@software{magland_figpack_2025,
|
|
125
|
+
author = {Magland, Jeremy},
|
|
126
|
+
title = {figpack},
|
|
127
|
+
year = 2025,
|
|
128
|
+
publisher = {Zenodo},
|
|
129
|
+
doi = {10.5281/zenodo.17419621},
|
|
130
|
+
url = {https://doi.org/10.5281/zenodo.17419621}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Or in APA format:
|
|
135
|
+
|
|
136
|
+
> Magland, J. (2025). figpack (Version 0.2.40) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.17419621
|
|
137
|
+
|
|
117
138
|
## Contributing
|
|
118
139
|
|
|
119
140
|
Visit the [GitHub repository](https://github.com/flatironinstitute/figpack) for issues, contributions, and the latest updates.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
figpack/__init__.py,sha256=skZ1R6k19F5UFsi2LdLti5jyUb4LzRtZT1VVaXWy0cE,358
|
|
2
|
+
figpack/cli.py,sha256=TfSGSw2YUlO0JPlNb4DYko636rk7vV91tqL6QHBSgoQ,18464
|
|
3
|
+
figpack/extensions.py,sha256=mILB4_F1RHkca4I7t88zh74IX8VCmfT7XFZZT4XYdNw,13009
|
|
4
|
+
figpack/core/__init__.py,sha256=7zU6O1piTk07aeCfbU81QqTgSHIO2n5MZ4LFNmsrtfs,192
|
|
5
|
+
figpack/core/_bundle_utils.py,sha256=a0oPsqRsaQbrNcUKXHZTfE5uPtgmtqLJdQQPE1jS4_0,9299
|
|
6
|
+
figpack/core/_file_handler.py,sha256=T_Yq3aGcgfqbGCW31Ge9MseApH3zoZ8iZ2wTyb9cYuw,7113
|
|
7
|
+
figpack/core/_save_figure.py,sha256=ZboTlu5N609GmBlQ7rni5p7AP1mvCelEjHBHWeLWGOM,1166
|
|
8
|
+
figpack/core/_server_manager.py,sha256=2F1fo1aEcbbmQXiZ5wjJ1SpV4BqgtwAqq8KvQFrFVPo,16262
|
|
9
|
+
figpack/core/_show_view.py,sha256=nLsVOuuy5-NLSAK8qMjxmRart3TAL1wH1duTcIH_mGA,5187
|
|
10
|
+
figpack/core/_upload_bundle.py,sha256=cNoLSbsulmZ8R8BzohZLACDmwxdDN9f8TwO_fpDeSHc,15020
|
|
11
|
+
figpack/core/_view_figure.py,sha256=FVPvSOa1hW2Th-X778VwKytfNpudoFNb1LK7eY43p44,5568
|
|
12
|
+
figpack/core/_zarr_consolidate.py,sha256=uKcSq_ml0pBVztJJZwinXAiyyk2P28Ao12IbEA3niSU,6052
|
|
13
|
+
figpack/core/config.py,sha256=oOR7SlP192vuFhYlS-h14HnG-kd_3gaz0vshXch2RNc,173
|
|
14
|
+
figpack/core/extension_view.py,sha256=qh1g7H_YfOF0eLAl-c7uZlv7A8Ssm8VbrhRfGsjtHto,1322
|
|
15
|
+
figpack/core/figpack_extension.py,sha256=F4q6poPihwVnjWQtasZjvzB0IkqFfgJuv8Xb9N4eNOA,2260
|
|
16
|
+
figpack/core/figpack_view.py,sha256=LCwydF_M03gvlAGjU9CMWFMyXGlV0EkfhzgAU9gxPWc,7292
|
|
17
|
+
figpack/core/zarr.py,sha256=wKi8G2MksdTbZUA3Yc-huhIplrOWCgkmYnn8NMftA7k,1666
|
|
18
|
+
figpack/figpack-figure-dist/index.html,sha256=hclmcIxQE4wsyJhUGc_VEn_mOIIwYNlPxMYiu-5Rl0Q,721
|
|
19
|
+
figpack/figpack-figure-dist/assets/index-ST_DU17U.js,sha256=mW0CJUOVNkAq41pYztt4p-stkSXMyG3Jv295_YHLd28,1116532
|
|
20
|
+
figpack/figpack-figure-dist/assets/index-V5m_wCvw.css,sha256=WRtQLW6SNlTlLtepSOt89t1z41SD7XzYUyRldqowjMM,7286
|
|
21
|
+
figpack/figpack-figure-dist/assets/neurosift-logo-CLsuwLMO.png,sha256=g5m-TwrGh5f6-9rXtWV-znH4B0nHgc__0GWclRDLUHs,9307
|
|
22
|
+
figpack/views/Box.py,sha256=WTp69JiytmcocdkKR4k-qLKMB9soiu_8uOKHeO6mtUQ,2416
|
|
23
|
+
figpack/views/CaptionedView.py,sha256=Lgv4ZMs0LqHuzLIjEGVZhq2zzv-Ufa9pqUCyky8DuCY,1908
|
|
24
|
+
figpack/views/DataFrame.py,sha256=VGspmfWtnZ4Gvea5zd-ODpiJPQEp8gVv-ScDhVVCeyA,3400
|
|
25
|
+
figpack/views/Gallery.py,sha256=15ukt9CmgkbT8q_okEYYDESW1E7vOJkVPombSlrEWKw,3324
|
|
26
|
+
figpack/views/GalleryItem.py,sha256=b_upJno5P3ANSulbG-h3t6Xj56tPGJ7iVxqyiZu3zaQ,1244
|
|
27
|
+
figpack/views/Iframe.py,sha256=F7q46W2UO1oDcG0IpAWgIxbMtRo9dPORJY8Msu3Tm6Y,1050
|
|
28
|
+
figpack/views/Image.py,sha256=z9PEnaGk470Zy5q06e2hIoHV53XSGpouMGT4bZf4ixA,3737
|
|
29
|
+
figpack/views/LayoutItem.py,sha256=wy8DggkIzZpU0F1zFIBceS7HpBb6lu-A3hpYINQzedk,1595
|
|
30
|
+
figpack/views/Markdown.py,sha256=yKMnWpxT0o9tRsPHjbcdZCgXpE67WNV-R66EAHdE2nA,1301
|
|
31
|
+
figpack/views/MatplotlibFigure.py,sha256=697xTOkNxcwYZrLoYOzh4CuME4NDUpIYzX-ckLE5aWU,2422
|
|
32
|
+
figpack/views/MountainLayout.py,sha256=JGvrhzqLR2im5d-m0TsZNy06KOR5iGfDlinrRqHpQsQ,2680
|
|
33
|
+
figpack/views/MountainLayoutItem.py,sha256=arYO1pD9RpXfHQKxtFagl66bjqSzEdafIf8ldDEMTD0,1451
|
|
34
|
+
figpack/views/MultiChannelTimeseries.py,sha256=6AkEbAsdM6fvZVsa3jakIjEcx6LNWhF0fbS00e33heM,8291
|
|
35
|
+
figpack/views/Spectrogram.py,sha256=YuxEbqDJhhD6R4z6aIR8zzPtk6Wcszjq5V5NtY_1s8w,9502
|
|
36
|
+
figpack/views/Splitter.py,sha256=BR2L-8aqicTubS1rSzsQ3XnhoJcX5GcfEnVWtEWEs0w,2016
|
|
37
|
+
figpack/views/TabLayout.py,sha256=AqdHPLcP2-caWjxbkC8r8m60z8n_eyZrIBGOOPSVNCs,1908
|
|
38
|
+
figpack/views/TabLayoutItem.py,sha256=xmHA0JsW_6naJze4_mQuP_Fy0Nm17p2N7w_AsmVRp8k,880
|
|
39
|
+
figpack/views/TimeseriesGraph.py,sha256=0s0Uc-4iuvnoosAh5-yJDYe0UyvWDMIJOqlNqb_dZdA,21931
|
|
40
|
+
figpack/views/__init__.py,sha256=eZVhNCRRUIYtXou__k2tfNjPKyrc576whfZsVFagofY,709
|
|
41
|
+
figpack/views/PlotlyExtension/PlotlyExtension.py,sha256=MehIrw3ZMjHc4LjKd6ZYKnMi0828bX2ehi68KcW4_P8,2125
|
|
42
|
+
figpack/views/PlotlyExtension/__init__.py,sha256=80Wy1mDMWyagjuR99ECxJePIYpRQ6TSyHkB0uZoBZ_0,70
|
|
43
|
+
figpack/views/PlotlyExtension/_plotly_extension.py,sha256=yZjG1NMGlQedeeLdV6TQWpi_NTm5Wfk5eWbXEdZbbFE,1455
|
|
44
|
+
figpack/views/PlotlyExtension/plotly_view.js,sha256=9BjgOPkqGl87SSonnb48nFeQV3UTIi1trpSPxd9qlKo,3055
|
|
45
|
+
figpack-0.2.40.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
46
|
+
figpack-0.2.40.dist-info/METADATA,sha256=QFIwOS4GN3o3vfLxr4OdSIEvdOGlJQyI1mNAojlQKcE,5196
|
|
47
|
+
figpack-0.2.40.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
48
|
+
figpack-0.2.40.dist-info/entry_points.txt,sha256=l6d3siH2LxXa8qJGbjAqpIZtI5AkMSyDeoRDCzdrUto,45
|
|
49
|
+
figpack-0.2.40.dist-info/top_level.txt,sha256=lMKGaC5xWmAYBx9Ac1iMokm42KFnJFjmkP2ldyvOo-c,8
|
|
50
|
+
figpack-0.2.40.dist-info/RECORD,,
|
figpack-0.2.27.dist-info/RECORD
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
figpack/__init__.py,sha256=zxhqr0lde2J_mnAi73-7r4CZptcqrtGZIZB7Tj5AJos,358
|
|
2
|
-
figpack/cli.py,sha256=s1mGQuFSntxiIvU6OWwHVlM9Cj-l1zMQ3OzFFe1-5ZE,11089
|
|
3
|
-
figpack/extensions.py,sha256=mILB4_F1RHkca4I7t88zh74IX8VCmfT7XFZZT4XYdNw,13009
|
|
4
|
-
figpack/core/__init__.py,sha256=7zU6O1piTk07aeCfbU81QqTgSHIO2n5MZ4LFNmsrtfs,192
|
|
5
|
-
figpack/core/_bundle_utils.py,sha256=LXPbINsm01jkbQeikgx-CauPZYj9uGFFApWwuzDlJPg,9063
|
|
6
|
-
figpack/core/_file_handler.py,sha256=TC4Z23Xy4dKky8BEwyp3Gj4K9-gpnKRiF1u6KkxWprc,6984
|
|
7
|
-
figpack/core/_save_figure.py,sha256=52ny-m1ThGgxY4ZgMn7m33n8A2OCyezj8H8TMvw0Y8Q,1043
|
|
8
|
-
figpack/core/_server_manager.py,sha256=I8PQImUa4C0L6LfIiCnpRnfQT7B-XVseCs80qnUvIlI,12374
|
|
9
|
-
figpack/core/_show_view.py,sha256=noADkomP1ovH-Jb7Bj0unmkPXvS_uOkkCILJSWag1RI,5171
|
|
10
|
-
figpack/core/_upload_bundle.py,sha256=yGngZfa6yzxh3-YDJrLuN5JaMd-egSxv6DWry67iuZY,14831
|
|
11
|
-
figpack/core/_view_figure.py,sha256=GYTgSCWBxi1pR16aBgAYRcEuFcgj0vjsrLIVfy1HYzM,5374
|
|
12
|
-
figpack/core/config.py,sha256=oOR7SlP192vuFhYlS-h14HnG-kd_3gaz0vshXch2RNc,173
|
|
13
|
-
figpack/core/extension_view.py,sha256=FSBXdhFEWicLi0jhkuRdS-a8CNsULrEqqIKtYfV3tmI,1255
|
|
14
|
-
figpack/core/figpack_extension.py,sha256=KSJKlnLYueFnGa8QFMpbIF3CDMwnIZJOqsI0smz6cUc,2252
|
|
15
|
-
figpack/core/figpack_view.py,sha256=rD94SehOcb_OVlZJuVK9UdH-dOJ-Mjlvg5cX1JEoH0w,6853
|
|
16
|
-
figpack/core/zarr.py,sha256=LTWOIX6vuH25STYTQS9_apfnfYXmATAEQkil3z9eYKE,1634
|
|
17
|
-
figpack/figpack-figure-dist/index.html,sha256=RA68WkQC-75AbzX32U3SGbbipP9NpkAMSXJWrfVCPkI,688
|
|
18
|
-
figpack/figpack-figure-dist/assets/index-DnHZdWys.js,sha256=YxHgJeAv3feDI2INjrt9_7a86niyJ8eL7gqaRxU-Zs4,1108653
|
|
19
|
-
figpack/figpack-figure-dist/assets/index-V5m_wCvw.css,sha256=WRtQLW6SNlTlLtepSOt89t1z41SD7XzYUyRldqowjMM,7286
|
|
20
|
-
figpack/figpack-figure-dist/assets/neurosift-logo-CLsuwLMO.png,sha256=g5m-TwrGh5f6-9rXtWV-znH4B0nHgc__0GWclRDLUHs,9307
|
|
21
|
-
figpack/views/Box.py,sha256=oN_OJH2pK_hH26k0eFCFjlfuJssVqKvw20GxYK1HX7g,2419
|
|
22
|
-
figpack/views/DataFrame.py,sha256=VGspmfWtnZ4Gvea5zd-ODpiJPQEp8gVv-ScDhVVCeyA,3400
|
|
23
|
-
figpack/views/Gallery.py,sha256=15ukt9CmgkbT8q_okEYYDESW1E7vOJkVPombSlrEWKw,3324
|
|
24
|
-
figpack/views/GalleryItem.py,sha256=b_upJno5P3ANSulbG-h3t6Xj56tPGJ7iVxqyiZu3zaQ,1244
|
|
25
|
-
figpack/views/Image.py,sha256=Nc8XNKQBm79iN6omZIsYEU6daNa_X3_IIbmt4q1Zb8k,3741
|
|
26
|
-
figpack/views/LayoutItem.py,sha256=wy8DggkIzZpU0F1zFIBceS7HpBb6lu-A3hpYINQzedk,1595
|
|
27
|
-
figpack/views/Markdown.py,sha256=ojUmeVf7gEh4nUSnbyg62dKNpwgQOLFDYV84w2NS5EQ,1133
|
|
28
|
-
figpack/views/MatplotlibFigure.py,sha256=697xTOkNxcwYZrLoYOzh4CuME4NDUpIYzX-ckLE5aWU,2422
|
|
29
|
-
figpack/views/MountainLayout.py,sha256=JGvrhzqLR2im5d-m0TsZNy06KOR5iGfDlinrRqHpQsQ,2680
|
|
30
|
-
figpack/views/MountainLayoutItem.py,sha256=arYO1pD9RpXfHQKxtFagl66bjqSzEdafIf8ldDEMTD0,1451
|
|
31
|
-
figpack/views/MultiChannelTimeseries.py,sha256=6AkEbAsdM6fvZVsa3jakIjEcx6LNWhF0fbS00e33heM,8291
|
|
32
|
-
figpack/views/Spectrogram.py,sha256=jcm26ucHedKDnBA5xnAUu9tW-g-ZutT-kw1EIhYm66E,9335
|
|
33
|
-
figpack/views/Splitter.py,sha256=BR2L-8aqicTubS1rSzsQ3XnhoJcX5GcfEnVWtEWEs0w,2016
|
|
34
|
-
figpack/views/TabLayout.py,sha256=AqdHPLcP2-caWjxbkC8r8m60z8n_eyZrIBGOOPSVNCs,1908
|
|
35
|
-
figpack/views/TabLayoutItem.py,sha256=xmHA0JsW_6naJze4_mQuP_Fy0Nm17p2N7w_AsmVRp8k,880
|
|
36
|
-
figpack/views/TimeseriesGraph.py,sha256=ygbgB83CIc5z_FN6gedWB_KIGh9Zo6LwrCafJqWckfU,19011
|
|
37
|
-
figpack/views/__init__.py,sha256=V09R6vFRzhY7ANevWomM7muFfUieXZEjGimPiMHpey4,641
|
|
38
|
-
figpack/views/PlotlyExtension/PlotlyExtension.py,sha256=LOFSqbm46UZ7HsHTDxUPnNB33ydYQvEkRVK-TSKkzK4,2149
|
|
39
|
-
figpack/views/PlotlyExtension/__init__.py,sha256=80Wy1mDMWyagjuR99ECxJePIYpRQ6TSyHkB0uZoBZ_0,70
|
|
40
|
-
figpack/views/PlotlyExtension/_plotly_extension.py,sha256=yZjG1NMGlQedeeLdV6TQWpi_NTm5Wfk5eWbXEdZbbFE,1455
|
|
41
|
-
figpack/views/PlotlyExtension/plotly_view.js,sha256=9BjgOPkqGl87SSonnb48nFeQV3UTIi1trpSPxd9qlKo,3055
|
|
42
|
-
figpack-0.2.27.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
43
|
-
figpack-0.2.27.dist-info/METADATA,sha256=1_RNdRpyaQQaJD0ZoqyWjWNAn2WcgrIdlfDLfFGYtes,4618
|
|
44
|
-
figpack-0.2.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
45
|
-
figpack-0.2.27.dist-info/entry_points.txt,sha256=l6d3siH2LxXa8qJGbjAqpIZtI5AkMSyDeoRDCzdrUto,45
|
|
46
|
-
figpack-0.2.27.dist-info/top_level.txt,sha256=lMKGaC5xWmAYBx9Ac1iMokm42KFnJFjmkP2ldyvOo-c,8
|
|
47
|
-
figpack-0.2.27.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|