File size: 4,346 Bytes
f12ecf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.13.0';

// Since we will download the model from the Hugging Face Hub, we can skip the local model check
env.allowLocalModels = false;

// Reference the elements that we will need
const status = document.getElementById('status');
const fileUpload = document.getElementById('upload');
const imageContainer = document.getElementById('container');
const example = document.getElementById('example');

const EXAMPLE_URL = 'https://i.imgur.com/mLvqQws.jpg';

// Create a new image segmentation pipeline
status.textContent = 'Loading model...';
const segmenter = await pipeline('image-segmentation', 'Xenova/face-parsing');
status.textContent = 'Ready';

example.addEventListener('click', (e) => {
    e.preventDefault();
    segment(EXAMPLE_URL);
});

fileUpload.addEventListener('change', function (e) {
    const file = e.target.files[0];
    if (!file) {
        return;
    }

    const reader = new FileReader();

    // Set up a callback when the file is loaded
    reader.onload = e2 => segment(e2.target.result);

    reader.readAsDataURL(file);
});

// Perform image segmentation
async function segment(img) {
    imageContainer.innerHTML = '';
    imageContainer.style.backgroundImage = `url(${img})`;

    status.textContent = 'Analysing...';
    const output = await segmenter(img);
    status.textContent = '';
    output.forEach(renderMask);
}

// Mapping of label to colour
const colours = [
    [234, 76, 76], // red
    [28, 180, 129], // sea green
    [234, 155, 21], // orange
    [67, 132, 243], // blue
    [243, 117, 36], // orange-red
    [145, 98, 243], // purple
    [21, 178, 208], // cyan
    [132, 197, 33], // lime
];

// Render a mask on the image
function renderMask({mask, label}, i) {
    // Create new canvas
    const canvas = document.createElement('canvas');
    canvas.width = mask.width;
    canvas.height = mask.height;
    canvas.setAttribute('data-label', label);
    
    // Create context and allocate buffer for pixel data
    const context = canvas.getContext('2d');
    const imageData = context.createImageData(canvas.width, canvas.height);
    const pixelData = imageData.data;

    // Choose colour based on index
    const [r, g, b] = colours[i % colours.length];

    // Fill mask with colour
    for (let i = 0; i < pixelData.length; ++i) {
        if (mask.data[i] !== 0) {
            const offset = 4 * i;
            pixelData[offset] = r;          // red
            pixelData[offset + 1] = g;      // green
            pixelData[offset + 2] = b;      // blue
            pixelData[offset + 3] = 255;    // alpha (fully opaque)
        }
    }
    
    // Draw image data to context
    context.putImageData(imageData, 0, 0);

    // Add canvas to container    
    imageContainer.appendChild(canvas);
}

// Clamp a value inside a range [min, max]
function clamp(x, min=0, max=1) {
    return Math.max(Math.min(x, max), min)
}

// Attach hover event to image container
imageContainer.addEventListener('mousemove', e => {
    const canvases = imageContainer.getElementsByTagName('canvas');
    if (canvases.length === 0) return;

    // Get bounding box
    const bb = imageContainer.getBoundingClientRect();
    
    // Get the mouse coordinates relative to the container
    const mouseX = clamp((e.clientX - bb.left) / bb.width);
    const mouseY = clamp((e.clientY - bb.top) / bb.height);
        
    // Loop over all canvases
    for (const canvas of canvases) {

        const canvasX = canvas.width * mouseX;
        const canvasY = canvas.height * mouseY;

        // Get the pixel data of the mouse coordinates
        const context = canvas.getContext('2d');
        const pixelData = context.getImageData(canvasX, canvasY, 1, 1).data;

        // Apply hover effect if not fully opaque
        if (pixelData[3] < 255) {
            canvas.style.opacity = 0.1;
        } else {
            canvas.style.opacity = 0.8;
            status.textContent = canvas.getAttribute('data-label');
        }
    }
});

// Reset canvas opacities on mouse exit
imageContainer.addEventListener('mouseleave', e => {
    const canvases = [...imageContainer.getElementsByTagName('canvas')];
    if (canvases.length > 0) {
        canvases.forEach(c => c.style.opacity = 0.6);
        status.textContent = '';
    }
})