menorki commited on
Commit
4d9baa7
1 Parent(s): 95b3af2
app.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from gradio_imageslider import ImageSlider
3
+ import os
4
+ import pprint
5
+ import sys
6
+ from pathlib import Path
7
+ from collections import OrderedDict
8
+ from PIL import Image
9
+
10
+
11
+ print('\n\n================================ START')
12
+
13
+ SEP = os.path.sep
14
+ BASE_URL = "https://huggingface.co/spaces/menorki/projet-moulin-belle-ile/resolve/main/"
15
+ BASE_PATH = Path(__file__).resolve().parent
16
+ BASE_DIR = str(BASE_PATH) + SEP
17
+ IN_SPACE = os.environ.get("SPACE_AUTHOR_NAME") in ["menorki"]
18
+
19
+ TMP_PATH = BASE_PATH / 'gradio_tmp'
20
+ TMP_DIR = str(TMP_PATH) + SEP
21
+ #if TMP_DIR.exists():
22
+ # shutil.rmtree(str(TMP_DIR))
23
+ TMP_PATH.mkdir(exist_ok=True, parents=True)
24
+ os.environ['GRADIO_TEMP_DIR'] = TMP_DIR
25
+
26
+ print(f"BASE_URL: {BASE_URL}")
27
+ print(f"BASE_DIR: {BASE_DIR}")
28
+ print(f"TMP_DIR: {TMP_DIR}")
29
+ print(f"IN_SPACE: {IN_SPACE}")
30
+
31
+ dir_models3D = "assets/windmill/models3D/"
32
+ dir_images = "assets/windmill/images/"
33
+ dir_videos = "assets/windmill/videos/"
34
+
35
+ # gr.set_static_paths(paths=[f"{BASE_DIR}assets/windmill"])
36
+
37
+ model3D_names = [f for f in os.listdir(dir_models3D) if f.endswith('.gltf') or f.endswith('.obj') or f.endswith('.glb')]
38
+ model3D_files = [BASE_URL + dir_models3D + name for name in model3D_names]
39
+
40
+ images_names = [f for f in os.listdir(dir_images) if f.endswith('.jpg') or f.endswith('.png')]
41
+ images_files = [(BASE_URL + dir_images + name , name) for name in images_names]
42
+
43
+ video_names = [f for f in os.listdir(dir_videos) if f.endswith('.mp4') or f.endswith('.avi')]
44
+ video_files = [BASE_URL + dir_videos + name for name in video_names]
45
+
46
+
47
+ DESCRIPTION = """# PROJET MOULIN SIMON """
48
+
49
+ css = '''
50
+ .gradio-container {max-width: 1280px !important; height:90%;}
51
+ #gallery { height: 90% !important; } /* Adjusted for padding/margin if any */
52
+ h1{text-align:center}
53
+ '''
54
+
55
+ head = '''
56
+ <script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.5.0/model-viewer.min.js"></script>
57
+ <script defer src="https://cdn.jsdelivr.net/npm/img-comparison-slider@8/dist/index.js"></script>
58
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/img-comparison-slider@8/dist/styles.css"/>
59
+ <style>
60
+ .custom-animated-handle {
61
+ transition: transform 0.2s;
62
+ }
63
+
64
+ .slider-with-animated-handle:hover .custom-animated-handle {
65
+ transform: scale(1.2);
66
+ }
67
+
68
+ h1, h2 {
69
+ color: #0056b3;
70
+ # padding-bottom:10px;
71
+ padding-top:10px;
72
+ }
73
+ p {
74
+ margin: 1em 0;
75
+ }
76
+ .highlight {
77
+ color: #d9534f;
78
+ font-weight: bold;
79
+ }
80
+ .emphasis {
81
+ color: #5cb85c;
82
+ font-style: italic;
83
+ }
84
+ </style>
85
+ '''
86
+
87
+ def read_text_file(path:str)->str :
88
+ with open(path, 'r', encoding='utf-8') as file:
89
+ content = file.read()
90
+ return content
91
+
92
+ def read_PIL_images(filenames):
93
+ images = []
94
+ for filename in filenames:
95
+ try:
96
+ print(f'Loading {filename}......')
97
+ img = Image.open(filename)
98
+ images.append(img)
99
+ print(f'Loaded {filename}')
100
+ except Exception as e:
101
+ print(f"Error opening image {filename}: {e}")
102
+ return images
103
+
104
+ def build_comparison_slider(before_url , after_url):
105
+ # @see https://img-comparison-slider.sneas.io/examples.html#always-show
106
+ html = f'''
107
+ <img-comparison-slider class="slider-with-animated-handle" style="max-width:500px; --divider-width: 4px; --divider-color: #ff0000;">
108
+ <img slot="first" width="100%" src="{before_url}" />
109
+ <img slot="second" width="100%" src="{after_url}" />
110
+ <svg slot="handle" class="custom-animated-handle" xmlns="http://www.w3.org/2000/svg" width="100" viewBox="-8 -3 16 6">
111
+ <path stroke="#fff" d="M -5 -2 L -7 0 L -5 2 M -5 -2 L -5 2 M 5 -2 L 7 0 L 5 2 M 5 -2 L 5 2" stroke-width="1" fill="#fff" vector-effect="non-scaling-stroke"></path>
112
+ </svg>
113
+ </img-comparison-slider>'''
114
+
115
+ return html
116
+
117
+ with gr.Blocks(analytics_enabled=False , head=head , css=css, theme="bethecloud/storj_theme" , elem_id='gradio-container') as demo:
118
+ gr.HTML(f'''<div style="width:100%; text-align:left"><img src="{BASE_URL}assets/images/banner-dugrainaupain.jpg" style="display: inline-block;"></div>''')
119
+
120
+
121
+ with gr.Tab("LE PROJET....."):
122
+
123
+ with gr.Row():
124
+ with gr.Column(scale=1):
125
+ before = BASE_URL + 'assets/images/before-after/moulin-insitu-1-_1610534-BEFORE.jpg'
126
+ after = BASE_URL + 'assets/images/before-after/moulin-insitu-1-_1610534-AFTER.jpg'
127
+ gr.HTML(build_comparison_slider(before , after))
128
+
129
+ with gr.Column(scale=1):
130
+ gr.HTML(read_text_file("assets/html/intro.txt"))
131
+
132
+
133
+ with gr.Tab("Avant/Après"):
134
+ with gr.Row():
135
+ before = BASE_URL + 'assets/images/before-after/moulin-insitu-1-_1610534-schema-BEFORE.jpg'
136
+ after = BASE_URL + 'assets/images/before-after/moulin-insitu-1-_1610534-AFTER.jpg'
137
+ gr.HTML(build_comparison_slider(before , after))
138
+
139
+ before = BASE_URL + 'assets/images/before-after/moulin-insitu-1-_1610534-BEFORE.jpg'
140
+ after = BASE_URL + 'assets/images/before-after/moulin-insitu-1-_1610534-AFTER.jpg'
141
+ gr.HTML(build_comparison_slider(before , after))
142
+
143
+ print(f'{BASE_URL}assets/images/before-after/moulin-insitu-1-_1610534-schema-BEFORE.jpg')
144
+ print(f'{BASE_URL}assets/images/before-after/moulin-insitu-1-_1610534-BEFORE.jpg')
145
+ print(f'{BASE_URL}assets/images/before-after/moulin-insitu-1-_1610534-AFTER.jpg')
146
+
147
+
148
+ with gr.Tab("Images"):
149
+ image_viewer = gr.Gallery(label="Generated images",
150
+ show_label=False,
151
+ elem_id='gallery',
152
+ columns=[3],
153
+ rows=[1],
154
+ object_fit="cover",
155
+ interactive=False,
156
+ value=images_files)
157
+
158
+
159
+ with gr.Tab("Modèles 3D"):
160
+
161
+ viewer_html = '''<div id="google-3D-viewer-container" style="min-width:800px; width:100%; height:600px;" style="flex: 1; display: flex; justify-content: center; align-items: center;">
162
+ <model-viewer id="google-3D-viewer" style="width: 100%; height: 100%;" src="DEFAULT_MODEL_URL" auto-rotate camera-controls></model-viewer>
163
+ </div>
164
+ '''
165
+
166
+ viewer_html = viewer_html.replace('DEFAULT_MODEL_URL' , BASE_URL + dir_models3D + model3D_names[0])
167
+ viewer_html = viewer_html.replace('BASE_URL_3D' , BASE_URL + dir_models3D)
168
+
169
+ google_viewer = gr.HTML(viewer_html)
170
+
171
+ with gr.Row(visible=True):
172
+
173
+ model_selection = gr.Radio(
174
+ show_label=True,
175
+ container=True,
176
+ interactive=True,
177
+ choices=model3D_names,
178
+ value=model3D_names[0],
179
+ label="Selectionner un modèle 3D :",
180
+ )
181
+
182
+ # js = f"(name) => document.getElementById('google-3D-viewer').src = '{BASE_URL + dir_models3D}' + name"
183
+ model_selection.change(fn=None,
184
+ inputs=model_selection,
185
+ outputs=None ,
186
+ js=f"(name) => document.getElementById('google-3D-viewer').src = '{BASE_URL + dir_models3D}' + name")
187
+
188
+
189
+ with gr.Tab("Videos"):
190
+
191
+ video_viewer = gr.Video(interactive=False, value=video_files[0])
192
+
193
+ with gr.Row(visible=True):
194
+
195
+ video_selection = gr.Radio(
196
+ show_label=True,
197
+ container=True,
198
+ interactive=True,
199
+ choices=video_names,
200
+ value=video_names[0],
201
+ label="Selectionner une video:"
202
+ )
203
+
204
+ def load_video(video_name):
205
+ video_file = BASE_URL + dir_videos + video_name
206
+ print(f'LOADING VIDEO: {video_file}')
207
+ return video_file
208
+
209
+ video_selection.change(fn=load_video, inputs=video_selection, outputs=video_viewer)
210
+
211
+
212
+ if __name__ == "__main__":
213
+ demo.launch(debug=not IN_SPACE, show_api=False)
assets/html/intro.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="container">
2
+ <h2>Énergie Solidaire sur Belle-Île-en-Mer</h1>
3
+ <p><span class="highlight">Belle-Île-en-Mer</span>, joyau de la Bretagne, s'apprête à accueillir un projet ambitieux et unique en son genre : la construction d'un moulin à vent destiné à moudre du blé et à produire de la farine. Cette initiative est portée par l'association à but non lucratif <span class="highlight">"DU GRAIN AU PAIN"</span>.</p>
4
+
5
+ <h2>Un Projet pour Revitaliser l'Artisanat Local</h2>
6
+ <p>L'idée de ce projet est simple mais audacieuse : construire un moulin à vent traditionnel sur l'île pour moudre du blé cultivé localement. La farine produite sera ensuite vendue aux commerces locaux tels que les <span class="highlight">boulangeries</span> et les <span class="highlight">crêperies</span>. Ce projet vise non seulement à <span class="emphasis">revitaliser l'artisanat local</span>, mais aussi à promouvoir des pratiques agricoles durables et à renforcer l'économie de l'île.</p>
7
+
8
+ <h2>Un Patrimoine à Préserver</h2>
9
+ <p>Les moulins à vent font partie intégrante du patrimoine de Belle-Île-en-Mer. En construisant ce moulin, nous souhaitons rendre hommage à cette tradition et offrir à la communauté un symbole de <span class="emphasis">solidarité et de durabilité</span>. Le moulin sera également un lieu éducatif où les visiteurs pourront découvrir les méthodes de mouture traditionnelles et en apprendre davantage sur l'histoire de l'île.</p>
10
+
11
+ <h2>Participez à Notre Campagne de Financement Participatif</h2>
12
+ <p>Pour réaliser ce projet, nous lançons une campagne de <span class="highlight">financement participatif</span>. Chaque contribution, qu'elle soit petite ou grande, nous rapprochera de notre objectif. En soutenant ce projet, vous contribuez à la préservation de notre patrimoine et à la promotion d'une économie locale <span class="emphasis">durable</span>.</p>
13
+
14
+ <p>Rejoignez-nous dans cette aventure et aidez-nous à faire souffler un vent de solidarité sur Belle-Île-en-Mer. Ensemble, construisons le <span class="highlight">Moulin Simon</span> et faisons de ce rêve une réalité !</p>
15
+ </div>
assets/images/banner-dugrainaupain.jpg ADDED
assets/images/before-after/moulin-insitu-1-_1610534-AFTER.jpg ADDED
assets/images/before-after/moulin-insitu-1-_1610534-BEFORE.jpg ADDED
assets/images/before-after/moulin-insitu-1-_1610534-schema-BEFORE.jpg ADDED
assets/windmill/images/dummy.txt ADDED
File without changes
assets/windmill/models3D/dummy.txt ADDED
File without changes
assets/windmill/videos/dummy.txt ADDED
File without changes
infos.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ https://huggingface.co/spaces/menorki/projet-moulin-belle-ile
2
+ https://menorki-projet-moulin-belle-ile.hf.space/
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ gradio_imageslider
3
+ Pillow
script.js ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function demo_load(x) {
2
+ document.body.scrollTop = document.documentElement.scrollTop = 0;
3
+
4
+ function gradioApp() {
5
+ const elems = document.getElementsByTagName('gradio-app');
6
+ const elem = elems.length == 0 ? document : elems[0];
7
+
8
+ if (elem !== document) {
9
+ elem.getElementById = function(id) {
10
+ return document.getElementById(id);
11
+ };
12
+ }
13
+ return elem.shadowRoot ? elem.shadowRoot : elem;
14
+ }
15
+
16
+ function all_gallery_buttons() {
17
+ var allGalleryButtons = gradioApp().querySelectorAll('#outputgallery .thumbnail-item.thumbnail-small');
18
+ var visibleGalleryButtons = [];
19
+ allGalleryButtons.forEach(function(elem) {
20
+ if (elem.parentElement.offsetParent) {
21
+ visibleGalleryButtons.push(elem);
22
+ }
23
+ });
24
+ return visibleGalleryButtons;
25
+ }
26
+
27
+ function selected_gallery_button() {
28
+ return all_gallery_buttons().find(elem => elem.classList.contains('selected')) ?? null;
29
+ }
30
+
31
+ function selected_gallery_index() {
32
+ return all_gallery_buttons().findIndex(elem => elem.classList.contains('selected'));
33
+ }
34
+
35
+ function loadImg(src){
36
+ return new Promise((resolve, reject) => {
37
+ let img = new Image()
38
+ img.onload = () => resolve(img)
39
+ img.onerror = reject
40
+ img.src = src
41
+ })
42
+ }
43
+
44
+ async function resize_b64_img(b64_img, max_side=2048) {
45
+ var img = await loadImg(b64_img);
46
+ naturalWidth = img.naturalWidth;
47
+ naturalHeight = img.naturalHeight;
48
+
49
+ if (naturalWidth > max_side || naturalHeight > max_side) {
50
+ var width = 0;
51
+ var height = 0;
52
+ if (naturalWidth >= naturalHeight) {
53
+ width = max_side;
54
+ height = Math.ceil((max_side / naturalWidth) * naturalHeight);
55
+ } else {
56
+ height = max_side;
57
+ width = Math.ceil((max_side / naturalHeight) * naturalWidth);
58
+ }
59
+
60
+ var canvas = document.createElement('canvas');
61
+ ctx = canvas.getContext('2d');
62
+ canvas.width = width;
63
+ canvas.height = height;
64
+ ctx.drawImage(img, 0, 0, width, height);
65
+ return canvas.toDataURL();
66
+ }
67
+ return b64_img;
68
+ }
69
+
70
+ // fix image preview on mobile
71
+ function imageMaskResize() {
72
+ const canvases = gradioApp().querySelectorAll('#inputmask canvas');
73
+ if (!canvases.length) {
74
+ window.removeEventListener('resize', imageMaskResize);
75
+ return;
76
+ }
77
+
78
+ const wrapper = canvases[0].closest('.wrap');
79
+ const previewImage = wrapper.previousElementSibling;
80
+
81
+ if (!previewImage.complete) {
82
+ previewImage.addEventListener('load', imageMaskResize);
83
+ return;
84
+ }
85
+
86
+ const w = previewImage.width;
87
+ const h = previewImage.height;
88
+ const nw = previewImage.naturalWidth;
89
+ const nh = previewImage.naturalHeight;
90
+ const portrait = nh > nw;
91
+
92
+ const wW = Math.min(w, portrait ? h / nh * nw : w / nw * nw);
93
+ const wH = Math.min(h, portrait ? h / nh * nh : w / nw * nh);
94
+
95
+ wrapper.style.width = `${wW}px`;
96
+ wrapper.style.height = `${wH}px`;
97
+ wrapper.style.left = `0px`;
98
+ wrapper.style.top = `0px`;
99
+
100
+ canvases.forEach(c => {
101
+ c.style.width = c.style.height = '';
102
+ c.style.maxWidth = '100%';
103
+ c.style.maxHeight = '100%';
104
+ c.style.objectFit = 'contain';
105
+ });
106
+ }
107
+
108
+ window.gradioApp = gradioApp
109
+ window.all_gallery_buttons = all_gallery_buttons
110
+ window.selected_gallery_button = selected_gallery_button
111
+ window.selected_gallery_index = selected_gallery_index
112
+ window.resize_b64_img = resize_b64_img
113
+ window.imageMaskResize = imageMaskResize;
114
+
115
+ window.addEventListener('resize', imageMaskResize);
116
+ }