showtens 0.0__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.
- showtens/__init__.py +3 -0
- showtens/images.py +115 -0
- showtens/imports.py +35 -0
- showtens/util.py +72 -0
- showtens/videos.py +104 -0
- showtens-0.0.dist-info/METADATA +49 -0
- showtens-0.0.dist-info/RECORD +9 -0
- showtens-0.0.dist-info/WHEEL +5 -0
- showtens-0.0.dist-info/top_level.txt +1 -0
showtens/__init__.py
ADDED
showtens/images.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
from .imports import import_torch, import_torchvision
|
|
4
|
+
from .util import gridify, _create_folder
|
|
5
|
+
|
|
6
|
+
torch = import_torch()
|
|
7
|
+
torchvision = import_torchvision()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@torch.no_grad()
|
|
11
|
+
def showImage(tensor, columns=None, colorbar=False, max_width=None, padding=3, pad_value=0.0):
|
|
12
|
+
""" "
|
|
13
|
+
Shows tensor as an image using pyplot.
|
|
14
|
+
Any extra dimensions (*,C,H,W) are treated as batch dimensions.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
tensor : (H,W) or (C,H,W) or (*,C,H,W) tensor to display
|
|
18
|
+
columns : number of columns to use for the grid of images (default 8 or less)
|
|
19
|
+
colorbar : whether to add a colorbar to the image, only works for grayscale images (default False)
|
|
20
|
+
max_width : maximum width of the image
|
|
21
|
+
padding : number of pixels between images in the grid
|
|
22
|
+
pad_value : inter-padding value for the grid of images
|
|
23
|
+
"""
|
|
24
|
+
tensor = _format_image(
|
|
25
|
+
tensor, columns=columns, max_width=max_width, padding=padding, pad_value=pad_value
|
|
26
|
+
) # (C,H',W') ready to show
|
|
27
|
+
|
|
28
|
+
plt.imshow(tensor.permute((1, 2, 0)))
|
|
29
|
+
plt.axis("off")
|
|
30
|
+
if tensor.shape[0] == 1 and colorbar:
|
|
31
|
+
plt.colorbar()
|
|
32
|
+
plt.show()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@torch.no_grad()
|
|
36
|
+
def saveImage(
|
|
37
|
+
tensor,
|
|
38
|
+
folder,
|
|
39
|
+
name="imagetensor",
|
|
40
|
+
columns=None,
|
|
41
|
+
colorbar=False,
|
|
42
|
+
max_width=None,
|
|
43
|
+
padding=3,
|
|
44
|
+
pad_value=0.0,
|
|
45
|
+
create_folder=True,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Saves tensor as a png image using pyplot.
|
|
49
|
+
Any extra dimensions (*,C,H,W) are treated as batch dimensions.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
tensor : (H,W) or (C,H,W) or (*,C,H,W) tensor to display
|
|
53
|
+
folder : relative path of folder where to save the image
|
|
54
|
+
name : name of the image (do not include extension)
|
|
55
|
+
columns : number of columns to use for the grid of images (default 8 or less)
|
|
56
|
+
colorbar : whether to add a colorbar to the image, only works for grayscale images (default False)
|
|
57
|
+
max_width : maximum width of the image
|
|
58
|
+
padding : number of pixels between images in the grid
|
|
59
|
+
pad_value : inter-padding value for the grid of images
|
|
60
|
+
create_folder : whether to create the folder if it does not exist (default True)
|
|
61
|
+
"""
|
|
62
|
+
_create_folder(folder, create_folder)
|
|
63
|
+
|
|
64
|
+
tensor = _format_image(
|
|
65
|
+
tensor, columns=columns, max_width=max_width, padding=padding, pad_value=pad_value
|
|
66
|
+
) # (C,H',W') ready to save
|
|
67
|
+
plt.imshow(tensor.permute((1, 2, 0)))
|
|
68
|
+
plt.axis("off")
|
|
69
|
+
if tensor.shape[0] == 1 and colorbar:
|
|
70
|
+
plt.colorbar()
|
|
71
|
+
|
|
72
|
+
plt.savefig(os.path.join(folder, f"{name}.png"), bbox_inches="tight", pad_inches=0)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@torch.no_grad()
|
|
76
|
+
def _format_image(tensor, columns=None, max_width=None, padding=3, pad_value=0.0):
|
|
77
|
+
""" "
|
|
78
|
+
Shows tensor as an image using pyplot.
|
|
79
|
+
Any extra dimensions (*,C,H,W) are treated as batch dimensions.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
tensor : (H,W) or (C,H,W) or (*,C,H,W) tensor to display
|
|
83
|
+
columns : number of columns to use for the grid of images (default 8 or less)
|
|
84
|
+
max_width : maximum width of the image
|
|
85
|
+
padding : number of pixels between images in the grid
|
|
86
|
+
pad_value : inter-padding value for the grid of images
|
|
87
|
+
"""
|
|
88
|
+
tensor = tensor.detach().cpu()
|
|
89
|
+
|
|
90
|
+
extra_params = dict(columns=columns, max_width=max_width, pad_value=pad_value, padding=padding)
|
|
91
|
+
if len(tensor.shape) == 2:
|
|
92
|
+
# Add batch and channel dimensions
|
|
93
|
+
return _format_image(tensor[None, :, :], **extra_params)
|
|
94
|
+
elif len(tensor.shape) == 3:
|
|
95
|
+
# Reached (C,H,W)
|
|
96
|
+
return tensor
|
|
97
|
+
elif len(tensor.shape) == 4:
|
|
98
|
+
# Gridify assuming (B,C,H,W)
|
|
99
|
+
B = tensor.shape[0]
|
|
100
|
+
if columns is not None:
|
|
101
|
+
numCol = columns
|
|
102
|
+
else:
|
|
103
|
+
numCol = min(8, B)
|
|
104
|
+
tensor = gridify(tensor, columns=numCol, max_width=max_width, pad_value=pad_value, padding=padding)
|
|
105
|
+
|
|
106
|
+
return _format_image(tensor, **extra_params)
|
|
107
|
+
elif len(tensor.shape) > 4:
|
|
108
|
+
# Collapse extra dimension to batch
|
|
109
|
+
tensor = tensor.reshape(
|
|
110
|
+
(-1, tensor.shape[-3], tensor.shape[-2], tensor.shape[-1])
|
|
111
|
+
) # assume all batch dimensions
|
|
112
|
+
print("Assuming extra dimension are all batch dimensions, newshape : ", tensor.shape)
|
|
113
|
+
return _format_image(tensor, **extra_params)
|
|
114
|
+
else:
|
|
115
|
+
raise Exception(f"Tensor shape should be (H,W), (C,H,W) or (*,C,H,W), but got : {tensor.shape} !")
|
showtens/imports.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
def import_torch():
|
|
2
|
+
"""
|
|
3
|
+
Lazily import torch, giving helpful error message if not installed.
|
|
4
|
+
"""
|
|
5
|
+
try:
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
return torch
|
|
9
|
+
except ImportError:
|
|
10
|
+
raise ImportError(
|
|
11
|
+
"PyTorch is not installed. ShowTens requires PyTorch. "
|
|
12
|
+
"Please install it with one of the following commands:\n\n"
|
|
13
|
+
"- For CPU only: pip install torch\n"
|
|
14
|
+
"- For CUDA support: Visit https://pytorch.org for installation instructions "
|
|
15
|
+
"specific to your system and CUDA version."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def import_torchvision():
|
|
20
|
+
"""
|
|
21
|
+
Lazily import torchvision.transforms, giving helpful error message if not installed.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
import torchvision
|
|
25
|
+
|
|
26
|
+
return torchvision
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise ImportError(
|
|
29
|
+
"torchvision is not installed. ShowTens requires torchvision. "
|
|
30
|
+
"Please install it with the following command:\n\n"
|
|
31
|
+
"pip install torchvision"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# torch = import_torch()
|
showtens/util.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from .imports import import_torch, import_torchvision
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
torch = import_torch()
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@torch.no_grad()
|
|
8
|
+
def gridify(tensor: torch.Tensor, max_width=None, columns=None, padding=3, pad_value=0.0):
|
|
9
|
+
"""
|
|
10
|
+
Makes a grid of images/videos from a batch of images.
|
|
11
|
+
Like torchvision's make_grid, but more flexible.
|
|
12
|
+
Accepts (B,*,H,W)
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
tensor : (B,*,H,W) tensor
|
|
16
|
+
max_width : max width of the output grid. Resizes images to fit the width
|
|
17
|
+
columns : number of columns of the grid. If None, uses 8 or less
|
|
18
|
+
padding : padding to add to the images
|
|
19
|
+
pad_value : color of the padding
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
(*,H',W') tensor, representing the grid of images/videos
|
|
23
|
+
"""
|
|
24
|
+
transf = import_torchvision().transforms
|
|
25
|
+
|
|
26
|
+
B, H, W = tensor.shape[0], tensor.shape[-2], tensor.shape[-1]
|
|
27
|
+
device = tensor.device
|
|
28
|
+
if columns is not None:
|
|
29
|
+
numCol = columns
|
|
30
|
+
else:
|
|
31
|
+
numCol = min(8, B)
|
|
32
|
+
|
|
33
|
+
black_cols = (-B) % numCol
|
|
34
|
+
tensor = torch.cat(
|
|
35
|
+
[tensor, torch.zeros(black_cols, *tensor.shape[1:], device=device)], dim=0
|
|
36
|
+
) # (B',*,H,W)
|
|
37
|
+
tensor = transf.Pad(padding, fill=pad_value)(tensor) # (B',*,H+padding*2,W+padding*2)
|
|
38
|
+
|
|
39
|
+
B, H, W = tensor.shape[0], tensor.shape[-2], tensor.shape[-1]
|
|
40
|
+
rest_dim = tensor.shape[1:-2]
|
|
41
|
+
|
|
42
|
+
rest_dim_prod = 1
|
|
43
|
+
for dim in rest_dim:
|
|
44
|
+
rest_dim_prod *= dim
|
|
45
|
+
|
|
46
|
+
if max_width is not None:
|
|
47
|
+
resize_ratio = max_width / (W * numCol)
|
|
48
|
+
if resize_ratio < 1:
|
|
49
|
+
indiv_tens_size = int(H * resize_ratio), int(W * resize_ratio)
|
|
50
|
+
tensor = tensor.reshape((B, rest_dim_prod, H, W))
|
|
51
|
+
tensor = transf.Resize(indiv_tens_size, antialias=True)(tensor) # (B',rest_dim_prod,H',W')
|
|
52
|
+
|
|
53
|
+
B, H, W = tensor.shape[0], tensor.shape[-2], tensor.shape[-1]
|
|
54
|
+
assert B % numCol == 0
|
|
55
|
+
|
|
56
|
+
numRows = B // numCol
|
|
57
|
+
|
|
58
|
+
tensor = tensor.reshape((numRows, numCol, rest_dim_prod, H, W)) # (numRows,numCol,rest_dim_prod,H',W')
|
|
59
|
+
tensor = torch.einsum("nmrhw->rnhmw", tensor) # (rest_prod,numRows,H',numCol,W')
|
|
60
|
+
tensor = tensor.reshape((rest_dim_prod, numRows * H, numCol * W)) # (rest_prod,numRows*H,numCol*W)
|
|
61
|
+
tensor = tensor.reshape((*rest_dim, numRows * H, numCol * W)) # (*,numRows*H,numCol*W)
|
|
62
|
+
|
|
63
|
+
return tensor
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@torch.no_grad()
|
|
67
|
+
def _create_folder(folder: str, create_folder: bool = True):
|
|
68
|
+
if create_folder:
|
|
69
|
+
os.makedirs(folder, exist_ok=True)
|
|
70
|
+
else:
|
|
71
|
+
if not (os.path.exists(folder)):
|
|
72
|
+
raise FileNotFoundError(f"Folder {folder} does not exist !")
|
showtens/videos.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from .util import gridify, import_torch, _create_folder
|
|
3
|
+
import cv2, numpy as np
|
|
4
|
+
|
|
5
|
+
torch = import_torch()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@torch.no_grad()
|
|
9
|
+
def showVideo(tensor, fps=30, columns=None, max_width=None, padding=3, pad_value=0.0):
|
|
10
|
+
"""
|
|
11
|
+
Shows tensor as a video. Accepts both (T,H,W), (T,3,H,W) and (*,T,3,H,W) float tensors.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
tensor : (T,H,W) or (T,3,H,W) or (*,T,3,H,W) float tensor
|
|
15
|
+
columns : number of columns to use for the grid of videos (default 8 or less)
|
|
16
|
+
fps : fps of the video (default 30)
|
|
17
|
+
out_size : Height of output video (height adapts to not deform videos) (default 800)
|
|
18
|
+
"""
|
|
19
|
+
return NotImplementedError("showVideo not implemented yet, use saveVideo instead")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@torch.no_grad()
|
|
23
|
+
def saveVideo(
|
|
24
|
+
tensor,
|
|
25
|
+
folder,
|
|
26
|
+
name="videotensor",
|
|
27
|
+
fps=30,
|
|
28
|
+
columns=None,
|
|
29
|
+
max_width=None,
|
|
30
|
+
padding=3,
|
|
31
|
+
pad_value=0.0,
|
|
32
|
+
create_folder=True,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Saves tensor as a video. Accepts both (T,H,W), (T,3,H,W) and (*,T,3,H,W) float tensors.
|
|
36
|
+
Assumes that the tensor value are in [0,1], clips them otherwise.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
tensor : (T,H,W) or (T,3,H,W) or (*,T,3,H,W) float tensor
|
|
40
|
+
folder : path to save the video
|
|
41
|
+
name : name of the video
|
|
42
|
+
fps : fps of the video (default 30)
|
|
43
|
+
columns : number of columns to use for the grid of videos (default 8 or less)
|
|
44
|
+
max_width : maximum width of the image
|
|
45
|
+
padding : number of pixels between images in the grid
|
|
46
|
+
pad_value : inter-padding value for the grid of images
|
|
47
|
+
"""
|
|
48
|
+
_create_folder(folder, create_folder)
|
|
49
|
+
|
|
50
|
+
tensor = _format_video(
|
|
51
|
+
tensor, columns=columns, fps=fps, max_width=max_width, padding=padding, pad_value=pad_value
|
|
52
|
+
) # (T,3,H',W') ready to save
|
|
53
|
+
T, C, H, W = tensor.shape
|
|
54
|
+
output_file = os.path.join(folder, f"{name}.mp4")
|
|
55
|
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v")
|
|
56
|
+
video = cv2.VideoWriter(output_file, fourcc, fps, (W, H))
|
|
57
|
+
|
|
58
|
+
to_save = (255 * tensor.permute(0, 2, 3, 1).cpu().numpy()).astype(np.uint8)
|
|
59
|
+
|
|
60
|
+
for t in range(T):
|
|
61
|
+
frame = to_save[t]
|
|
62
|
+
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
|
|
63
|
+
video.write(frame)
|
|
64
|
+
|
|
65
|
+
video.release()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _format_video(tensor, columns=None, fps=30, max_width=None, padding=3, pad_value=0.0):
|
|
69
|
+
"""
|
|
70
|
+
Formats tensor as a video. Accepts both (T,H,W), (T,3,H,W) and (*,T,3,H,W) float tensors.
|
|
71
|
+
Assumes that the tensor value are in [0,1], clips them otherwise.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
tensor : (T,H,W) or (T,3,H,W) or (*,T,3,H,W) float tensor
|
|
75
|
+
columns : number of columns to use for the grid of videos (default 8 or less)
|
|
76
|
+
fps : fps of the video (default 30)
|
|
77
|
+
max_width : maximum width of the image
|
|
78
|
+
padding : number of pixels between images in the grid
|
|
79
|
+
pad_value : inter-padding value for the grid of images
|
|
80
|
+
"""
|
|
81
|
+
tensor = tensor.detach().cpu()
|
|
82
|
+
extra_params = dict(columns=columns, fps=fps, max_width=max_width, pad_value=pad_value, padding=padding)
|
|
83
|
+
|
|
84
|
+
if len(tensor.shape) == 3:
|
|
85
|
+
# add channel dimension
|
|
86
|
+
tensor = tensor[:, None, :, :].expand(-1, 3, -1, -1) # (T,3,H,W)
|
|
87
|
+
return _format_video(tensor, **extra_params)
|
|
88
|
+
elif len(tensor.shape) == 4:
|
|
89
|
+
if tensor.shape[1] == 1:
|
|
90
|
+
print("Assuming gray-scale video")
|
|
91
|
+
tensor = tensor.expand(-1, 3, -1, -1) # (T,3,H,W)
|
|
92
|
+
assert tensor.shape[1] == 3, f"Tensor shape should be (T,H,W), (T,3,H,W) or (*,T,3,H,W) !"
|
|
93
|
+
# A single video
|
|
94
|
+
return tensor
|
|
95
|
+
elif len(tensor.shape) == 5:
|
|
96
|
+
tensor = gridify(tensor, max_width=max_width, columns=columns, padding=padding, pad_value=pad_value)
|
|
97
|
+
return _format_video(tensor, **extra_params)
|
|
98
|
+
elif len(tensor.shape) > 5:
|
|
99
|
+
tensor = tensor.reshape((-1, *tensor.shape[-4:]))
|
|
100
|
+
return _format_video(tensor, **extra_params)
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"Tensor shape should be (T,H,W), (T,3,H,W) or (*,T,3,H,W), but got : {tensor.shape} !"
|
|
104
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: showtens
|
|
3
|
+
Version: 0.0
|
|
4
|
+
Summary: Visualize torch tensors EASILY.
|
|
5
|
+
Author-email: Vassilis Papadopoulos <vassilis.physics@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Homepage, https://github.com/frotaur/showtens
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/frotaur/showtens/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: matplotlib
|
|
16
|
+
Requires-Dist: opencv-python
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
|
|
19
|
+
# ShowTens : visualize torch tensors EASILY
|
|
20
|
+
|
|
21
|
+
ShowTens is a simple pytorch package that allows painless and flexible visualizations of image and video tensors.
|
|
22
|
+
|
|
23
|
+
\<ADD VISUALIZATION VIDEO HERE\>
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
`pip install showtens`
|
|
28
|
+
|
|
29
|
+
Make sure `torch`and `torchvision` are installed, as the package depends on them.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
```python
|
|
33
|
+
import torch
|
|
34
|
+
from showTens import showImage
|
|
35
|
+
|
|
36
|
+
image1 = torch.rand((3,100,100)) # (C,H,W) image
|
|
37
|
+
showImage(image1) # Displays the image using matplotlib
|
|
38
|
+
image2 = torch.rand((4,4,3,100,100)) # (B1,B2,C,H,W), two batch dimensions
|
|
39
|
+
showImage(image2,colums=4) # Will display as a 4*4 grid
|
|
40
|
+
|
|
41
|
+
from showTens import saveImage
|
|
42
|
+
saveImage(tensor=image1,folder='saved_images',name='imagetensor')
|
|
43
|
+
|
|
44
|
+
from showTens import saveVideo
|
|
45
|
+
video1 = torch.rand((60,3,200,200))
|
|
46
|
+
saveVideo(tensor=video1,folder='save_videos',name='videotensor',fps=30)
|
|
47
|
+
video2 = torch.rand((4,3,200,200)) # (B,T,C,H,W), batch of videos
|
|
48
|
+
saveVideo(tensor=video2,folder='save_videos',name='videobatch',fps=30,columns=2) # 2*2 video grid
|
|
49
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
showtens/__init__.py,sha256=r0JRXhXHlVJmITQlPQINI0rtM7vf_Mn9EELcU_Wh4g8,98
|
|
2
|
+
showtens/images.py,sha256=-9RGBhpBXmXjV8d5c-M8df4mUddoZgivbaaCIwf01_A,4380
|
|
3
|
+
showtens/imports.py,sha256=xx9G4Ap3uTAlXxh7LaNvIz-P0rSMsiMxeGQc5oTo3Dw,1056
|
|
4
|
+
showtens/util.py,sha256=g_2B-KV_OSTakn9P6Ku2u6FTkxhbZfhywYpgnch4fgA,2592
|
|
5
|
+
showtens/videos.py,sha256=Gpvisiqq_7w0a9RhkXJOXEf9qdPW3ROTwrV-UYJDyIE,3995
|
|
6
|
+
showtens-0.0.dist-info/METADATA,sha256=6bGZPusjOFlZcMCq77U1bb1Y-Gdy8iicehfUbQLCo7E,1708
|
|
7
|
+
showtens-0.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
8
|
+
showtens-0.0.dist-info/top_level.txt,sha256=1SACMFm7OZu9Dl-DE-HZyHym7Lan02cXwMWAMWj5zsA,9
|
|
9
|
+
showtens-0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
showtens
|