Spaces:
Sleeping
Sleeping
File size: 5,134 Bytes
68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 3e3f7d8 68db483 cfca85e 68db483 cfca85e 68db483 41164c9 cfca85e 41164c9 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 945893c cfca85e 68db483 cfca85e bf1a819 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e bf1a819 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 cfca85e 68db483 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "marimo",
# "numba==0.60.0",
# "numpy==2.0.2",
# "scikit-image==0.24.0",
# ]
# ///
import marimo
__generated_with = "0.9.6"
app = marimo.App(width="medium")
@app.cell(hide_code=True)
def __(mo):
mo.md(
"""
# Seam Carving
_Example adapted from work by [Vincent Warmerdam](https://x.com/fishnets88)_.
## The seam carving algorithm
This marimo demonstration is partially an homage to [a great video by Grant
Sanderson](https://www.youtube.com/watch?v=rpB6zQNsbQU) of 3Blue1Brown, which demonstrates
the seam carving algorithm in [Pluto.jl](https://plutojl.org/):
<iframe width="560" height="315" src="https://www.youtube.com/embed/rpB6zQNsbQU?si=oiZclGIj2atJR47m" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
As Grant explains, the seam carving algorithm preserves the shapes of the main content in the image, while killing the "dead space": the image is resized, but the clocks and other content are not resized or deformed.
This notebook is a Python version of the seam carving algorithm, but it is also a
demonstration of marimo's [caching
feature](https://docs.marimo.io/guides/best_practices/performance.html#cache-computations-with-mo-cache),
which is helpful because the algorithm is compute intensive even when you
use [Numba](https://numba.pydata.org/).
Try it out by playing with the slider!
"""
)
return
@app.cell(hide_code=True)
def __():
input_image = "https://upload.wikimedia.org/wikipedia/en/d/dd/The_Persistence_of_Memory.jpg"
return input_image,
@app.cell(hide_code=True)
def __(mo):
mo.md("""## Try it!""")
return
@app.cell
def __():
import marimo as mo
slider = mo.ui.slider(
0.7,
1.0,
step=0.05,
value=1.0,
label="Amount of resizing to perform:",
show_value=True,
)
slider
return mo, slider
@app.cell
def __(efficient_seam_carve, input_image, mo, slider):
scale_factor = slider.value
result = efficient_seam_carve(input_image, scale_factor)
mo.hstack([mo.image(input_image), mo.image(result)], justify="start")
return result, scale_factor
@app.cell
def __(mo):
import numpy as np
from numba import jit
from skimage import io, filters, transform
import time
def rgb2gray(rgb):
return np.dot(rgb[..., :3], [0.2989, 0.5870, 0.1140])
def compute_energy_map(gray):
return np.abs(filters.sobel_h(gray)) + np.abs(filters.sobel_v(gray))
@jit(nopython=True)
def find_seam(energy_map):
height, width = energy_map.shape
dp = energy_map.copy()
backtrack = np.zeros((height, width), dtype=np.int32)
for i in range(1, height):
for j in range(width):
if j == 0:
idx = np.argmin(dp[i - 1, j : j + 2])
backtrack[i, j] = idx + j
min_energy = dp[i - 1, idx + j]
elif j == width - 1:
idx = np.argmin(dp[i - 1, j - 1 : j + 1])
backtrack[i, j] = idx + j - 1
min_energy = dp[i - 1, idx + j - 1]
else:
idx = np.argmin(dp[i - 1, j - 1 : j + 2])
backtrack[i, j] = idx + j - 1
min_energy = dp[i - 1, idx + j - 1]
dp[i, j] += min_energy
return backtrack
@jit(nopython=True)
def remove_seam(image, backtrack):
height, width, _ = image.shape
output = np.zeros((height, width - 1, 3), dtype=np.uint8)
j = np.argmin(backtrack[-1])
for i in range(height - 1, -1, -1):
for k in range(3):
output[i, :, k] = np.delete(image[i, :, k], j)
j = backtrack[i, j]
return output
def seam_carving(image, new_width):
height, width, _ = image.shape
while width > new_width:
gray = rgb2gray(image)
energy_map = compute_energy_map(gray)
backtrack = find_seam(energy_map)
image = remove_seam(image, backtrack)
width -= 1
return image
@mo.cache
def efficient_seam_carve(image_path, scale_factor):
img = io.imread(image_path)
new_width = int(img.shape[1] * scale_factor)
start_time = time.time()
carved_img = seam_carving(img, new_width)
end_time = time.time()
print(f"Seam carving completed in {end_time - start_time:.2f} seconds")
return carved_img
return (
compute_energy_map,
efficient_seam_carve,
filters,
find_seam,
io,
jit,
np,
remove_seam,
rgb2gray,
seam_carving,
time,
transform,
)
if __name__ == "__main__":
app.run() |