GitHub Actions commited on
Commit
bcd2be9
·
1 Parent(s): e1ca393

deploy: sync from GitHub 607d9479768586cfebd7ee10f60fa8ae777045be

Browse files
Files changed (4) hide show
  1. Dockerfile +8 -10
  2. README.md +1 -0
  3. requirements.txt +2 -3
  4. static/script.js +112 -125
Dockerfile CHANGED
@@ -1,14 +1,9 @@
1
- # Use an official Python runtime as a parent image
2
- FROM python:3.12
3
- # FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04
4
 
5
  # run updates before switching over to non-root user
6
  RUN apt-get update && apt-get install -y --no-install-recommends \
7
- libgl1 \
8
  libglib2.0-0 \
9
- libsm6 \
10
- libxrender1 \
11
- libxext6 \
12
  && rm -rf /var/lib/apt/lists/*
13
 
14
  # add new user with ID 1000 to avoid permission issues on HF spaces
@@ -17,7 +12,7 @@ USER user
17
 
18
  # Set home to user's home dir and add local bin to PATH
19
  ENV HOME=/home/user \
20
- PATH=/user/user/.local/bin:$PATH
21
 
22
  # Set the working directory in the container
23
  WORKDIR $HOME/app
@@ -25,7 +20,10 @@ WORKDIR $HOME/app
25
  # Try and run pip command after setting the user with `USER user` to avoid permission issues with Python
26
  # NOTE - this is from the HF Spaces docs, not sure if necessary
27
  COPY --chown=user ./requirements.txt .
28
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
 
 
 
29
 
30
  # Copy the current directory contents into the container at $HOME/app setting the owner to the user
31
  COPY --chown=user . $HOME/app
@@ -40,7 +38,7 @@ COPY --chown=user . $HOME/app
40
  RUN mkdir -p uploads results annotated .yolo_config
41
 
42
  # set the env var for YOLO user config directory
43
- ENV YOLO_CONFIG_DIR=.yolo_config
44
 
45
  # Copy the rest of the application code into the container at /app
46
  # This includes app.py, nemaquant.py, templates/, static/, etc.
 
1
+ # CPU image - use Dockerfile.gpu for GPU support
2
+ FROM python:3.12.13-slim-trixie
 
3
 
4
  # run updates before switching over to non-root user
5
  RUN apt-get update && apt-get install -y --no-install-recommends \
 
6
  libglib2.0-0 \
 
 
 
7
  && rm -rf /var/lib/apt/lists/*
8
 
9
  # add new user with ID 1000 to avoid permission issues on HF spaces
 
12
 
13
  # Set home to user's home dir and add local bin to PATH
14
  ENV HOME=/home/user \
15
+ PATH=/home/user/.local/bin:$PATH
16
 
17
  # Set the working directory in the container
18
  WORKDIR $HOME/app
 
20
  # Try and run pip command after setting the user with `USER user` to avoid permission issues with Python
21
  # NOTE - this is from the HF Spaces docs, not sure if necessary
22
  COPY --chown=user ./requirements.txt .
23
+ RUN pip install --no-cache-dir torch==2.7.1 torchvision --index-url https://download.pytorch.org/whl/cpu
24
+ RUN pip install --no-cache-dir --only-binary :all: -r requirements.txt
25
+ # Force headless opencv after ultralytics (which pulls in full opencv-python as a dependency)
26
+ RUN pip install --no-cache-dir --force-reinstall opencv-python-headless==4.13.0.92
27
 
28
  # Copy the current directory contents into the container at $HOME/app setting the owner to the user
29
  COPY --chown=user . $HOME/app
 
38
  RUN mkdir -p uploads results annotated .yolo_config
39
 
40
  # set the env var for YOLO user config directory
41
+ ENV YOLO_CONFIG_DIR=$HOME/app/.yolo_config
42
 
43
  # Copy the rest of the application code into the container at /app
44
  # This includes app.py, nemaquant.py, templates/, static/, etc.
README.md CHANGED
@@ -4,6 +4,7 @@ emoji: 🔬
4
  colorFrom: indigo
5
  colorTo: blue
6
  sdk: docker
 
7
  license: apache-2.0
8
  short_description: "YOLO-based nematode egg detection with real-time processing"
9
  tags:
 
4
  colorFrom: indigo
5
  colorTo: blue
6
  sdk: docker
7
+ dockerfile: Dockerfile.gpu
8
  license: apache-2.0
9
  short_description: "YOLO-based nematode egg detection with real-time processing"
10
  tags:
requirements.txt CHANGED
@@ -1,10 +1,9 @@
1
  Flask==3.1.1
2
  numpy==2.2.6
3
- opencv_python==4.12.0.88
4
  pandas==2.3.1
5
  Pillow==11.3.0
6
  torch==2.7.1
7
  ultralytics==8.3.170
8
  watchdog==6.0.0
9
- Werkzeug==3.1.3
10
- gunicorn==21.2.0
 
1
  Flask==3.1.1
2
  numpy==2.2.6
3
+ opencv_python-headless==4.13.0.92
4
  pandas==2.3.1
5
  Pillow==11.3.0
6
  torch==2.7.1
7
  ultralytics==8.3.170
8
  watchdog==6.0.0
9
+ Werkzeug==3.1.3
 
static/script.js CHANGED
@@ -118,29 +118,8 @@ document.addEventListener('DOMContentLoaded', () => {
118
  showSingleCsvDialog(csvFiles[0]);
119
  return;
120
  } else if (csvFiles.length === 0) {
121
- const wantToUpload = window.confirm('No CSV key file was found in the folder. Would you like to supply a CSV key file now?');
122
- if (wantToUpload) {
123
- // Create a hidden file input for CSV upload
124
- let csvInput = document.createElement('input');
125
- csvInput.type = 'file';
126
- csvInput.accept = '.csv,.CSV';
127
- csvInput.style.display = 'none';
128
- document.body.appendChild(csvInput);
129
- csvInput.addEventListener('change', function() {
130
- if (csvInput.files && csvInput.files.length === 1) {
131
- window.selectedKeyenceCsv = csvInput.files[0];
132
- logStatus(`CSV key file supplied: ${csvInput.files[0].name}`);
133
- } else {
134
- window.selectedKeyenceCsv = null;
135
- logStatus('No CSV key file supplied.');
136
- }
137
- document.body.removeChild(csvInput);
138
- });
139
- csvInput.click();
140
- } else {
141
- window.selectedKeyenceCsv = null;
142
- logStatus('No CSV key file will be used.');
143
- }
144
  } else {
145
  // Multiple CSVs found, show a custom dialog for selection
146
  showCsvSelectionDialog(csvFiles);
@@ -1310,8 +1289,7 @@ document.addEventListener('DOMContentLoaded', () => {
1310
  }
1311
 
1312
  // Custom dialog for selecting among multiple CSV files
1313
- function showCsvSelectionDialog(csvFiles) {
1314
- // Create overlay
1315
  const overlay = document.createElement('div');
1316
  overlay.style.position = 'fixed';
1317
  overlay.style.top = 0;
@@ -1324,77 +1302,93 @@ document.addEventListener('DOMContentLoaded', () => {
1324
  overlay.style.alignItems = 'center';
1325
  overlay.style.justifyContent = 'center';
1326
 
1327
- // Create dialog box
1328
  const dialog = document.createElement('div');
1329
  dialog.style.background = '#fff';
1330
  dialog.style.padding = '24px 32px';
1331
  dialog.style.borderRadius = '8px';
1332
  dialog.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)';
1333
  dialog.style.minWidth = '320px';
1334
- dialog.innerHTML = `<h3>Select a CSV key file</h3><p>Multiple CSV files were found. Please select one to use as the key file:</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1335
 
1336
  // List CSV files as buttons
1337
  csvFiles.forEach((file, idx) => {
1338
- const btn = document.createElement('button');
1339
- btn.textContent = file.name;
1340
- btn.style.display = 'block';
1341
- btn.style.margin = '8px 0';
1342
- btn.onclick = () => {
1343
  window.selectedKeyenceCsv = file;
1344
  logStatus(`CSV key file selected: ${file.name}`);
1345
  document.body.removeChild(overlay);
1346
- };
1347
  dialog.appendChild(btn);
1348
  });
1349
 
1350
  // Option: Use a different .csv
1351
- const otherBtn = document.createElement('button');
1352
- otherBtn.textContent = 'Use a different .csv';
1353
- otherBtn.style.display = 'block';
1354
- otherBtn.style.margin = '16px 0 8px 0';
1355
- otherBtn.onclick = () => {
1356
- let csvInput = document.createElement('input');
1357
- csvInput.type = 'file';
1358
- csvInput.accept = '.csv,.CSV';
1359
- csvInput.style.display = 'none';
1360
- document.body.appendChild(csvInput);
1361
- csvInput.addEventListener('change', function() {
1362
- if (csvInput.files && csvInput.files.length === 1) {
1363
- window.selectedKeyenceCsv = csvInput.files[0];
1364
- logStatus(`CSV key file supplied: ${csvInput.files[0].name}`);
1365
- } else {
1366
- window.selectedKeyenceCsv = null;
1367
- logStatus('No CSV key file supplied.');
1368
- }
1369
- document.body.removeChild(csvInput);
1370
  document.body.removeChild(overlay);
1371
  });
1372
- csvInput.click();
1373
- };
1374
  dialog.appendChild(otherBtn);
1375
 
1376
  // Option: No key file
1377
- const noneBtn = document.createElement('button');
1378
- noneBtn.textContent = 'No key file';
1379
- noneBtn.style.display = 'block';
1380
- noneBtn.style.margin = '8px 0';
1381
- noneBtn.onclick = () => {
1382
  window.selectedKeyenceCsv = null;
1383
  logStatus('No CSV key file will be used.');
1384
  document.body.removeChild(overlay);
1385
- };
1386
  dialog.appendChild(noneBtn);
1387
 
1388
  // Cancel button
1389
- const cancelBtn = document.createElement('button');
1390
- cancelBtn.textContent = 'Cancel';
1391
- cancelBtn.style.display = 'block';
1392
- cancelBtn.style.margin = '8px 0';
1393
- cancelBtn.onclick = () => {
1394
  // Do not change selection, just close dialog
1395
  logStatus('CSV key file selection cancelled.');
1396
  document.body.removeChild(overlay);
1397
- };
1398
  dialog.appendChild(cancelBtn);
1399
 
1400
  overlay.appendChild(dialog);
@@ -1403,86 +1397,79 @@ document.addEventListener('DOMContentLoaded', () => {
1403
 
1404
  // Custom dialog for single CSV file found
1405
  function showSingleCsvDialog(csvFile) {
1406
- const overlay = document.createElement('div');
1407
- overlay.style.position = 'fixed';
1408
- overlay.style.top = 0;
1409
- overlay.style.left = 0;
1410
- overlay.style.width = '100vw';
1411
- overlay.style.height = '100vh';
1412
- overlay.style.background = 'rgba(0,0,0,0.4)';
1413
- overlay.style.zIndex = 9999;
1414
- overlay.style.display = 'flex';
1415
- overlay.style.alignItems = 'center';
1416
- overlay.style.justifyContent = 'center';
1417
-
1418
- const dialog = document.createElement('div');
1419
- dialog.style.background = '#fff';
1420
- dialog.style.padding = '24px 32px';
1421
- dialog.style.borderRadius = '8px';
1422
- dialog.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)';
1423
- dialog.style.minWidth = '320px';
1424
- dialog.innerHTML = `<h3>CSV File Found</h3><p>A CSV file named <strong>${csvFile.name}</strong> was found in the folder. Would you like to use this as the key file for this folder?</p>`;
1425
 
1426
  // Yes button
1427
- const yesBtn = document.createElement('button');
1428
- yesBtn.textContent = 'Yes';
1429
- yesBtn.style.display = 'block';
1430
- yesBtn.style.margin = '8px 0';
1431
- yesBtn.onclick = () => {
1432
  window.selectedKeyenceCsv = csvFile;
1433
  logStatus(`CSV key file selected: ${csvFile.name}`);
1434
  document.body.removeChild(overlay);
1435
- };
1436
  dialog.appendChild(yesBtn);
1437
 
1438
  // Use a different .csv file
1439
- const otherBtn = document.createElement('button');
1440
- otherBtn.textContent = 'Use a different .csv file';
1441
- otherBtn.style.display = 'block';
1442
- otherBtn.style.margin = '8px 0';
1443
- otherBtn.onclick = () => {
1444
- let csvInput = document.createElement('input');
1445
- csvInput.type = 'file';
1446
- csvInput.accept = '.csv,.CSV';
1447
- csvInput.style.display = 'none';
1448
- document.body.appendChild(csvInput);
1449
- csvInput.addEventListener('change', function() {
1450
- if (csvInput.files && csvInput.files.length === 1) {
1451
- window.selectedKeyenceCsv = csvInput.files[0];
1452
- logStatus(`CSV key file supplied: ${csvInput.files[0].name}`);
1453
- } else {
1454
- window.selectedKeyenceCsv = null;
1455
- logStatus('No CSV key file supplied.');
1456
- }
1457
- document.body.removeChild(csvInput);
1458
  document.body.removeChild(overlay);
1459
  });
1460
- csvInput.click();
1461
- };
1462
  dialog.appendChild(otherBtn);
1463
 
1464
  // Do not use a key file
1465
- const noneBtn = document.createElement('button');
1466
- noneBtn.textContent = 'Do not use a key file';
1467
- noneBtn.style.display = 'block';
1468
- noneBtn.style.margin = '8px 0';
1469
- noneBtn.onclick = () => {
1470
  window.selectedKeyenceCsv = null;
1471
  logStatus('No CSV key file will be used.');
1472
  document.body.removeChild(overlay);
1473
- };
1474
  dialog.appendChild(noneBtn);
1475
 
1476
  // Cancel button
1477
- const cancelBtn = document.createElement('button');
1478
- cancelBtn.textContent = 'Cancel';
1479
- cancelBtn.style.display = 'block';
1480
- cancelBtn.style.margin = '8px 0';
1481
- cancelBtn.onclick = () => {
1482
  // Do not change selection, just close dialog
1483
  logStatus('CSV key file selection cancelled.');
1484
  document.body.removeChild(overlay);
1485
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1486
  dialog.appendChild(cancelBtn);
1487
 
1488
  overlay.appendChild(dialog);
@@ -1501,4 +1488,4 @@ document.addEventListener('DOMContentLoaded', () => {
1501
  confidenceSlider.value = 0.6;
1502
  confidenceSlider.dispatchEvent(new Event('input', { bubbles: true }));
1503
  confidenceSlider.dispatchEvent(new Event('change', { bubbles: true }));
1504
- });
 
118
  showSingleCsvDialog(csvFiles[0]);
119
  return;
120
  } else if (csvFiles.length === 0) {
121
+ showNoCsvDialog();
122
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  } else {
124
  // Multiple CSVs found, show a custom dialog for selection
125
  showCsvSelectionDialog(csvFiles);
 
1289
  }
1290
 
1291
  // Custom dialog for selecting among multiple CSV files
1292
+ function createDialogOverlay(titleText, messageContent) {
 
1293
  const overlay = document.createElement('div');
1294
  overlay.style.position = 'fixed';
1295
  overlay.style.top = 0;
 
1302
  overlay.style.alignItems = 'center';
1303
  overlay.style.justifyContent = 'center';
1304
 
 
1305
  const dialog = document.createElement('div');
1306
  dialog.style.background = '#fff';
1307
  dialog.style.padding = '24px 32px';
1308
  dialog.style.borderRadius = '8px';
1309
  dialog.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)';
1310
  dialog.style.minWidth = '320px';
1311
+ const title = document.createElement('h3');
1312
+ title.textContent = titleText;
1313
+ const message = document.createElement('p');
1314
+ if (typeof messageContent === 'string') {
1315
+ message.textContent = messageContent;
1316
+ } else if (messageContent) {
1317
+ message.appendChild(messageContent);
1318
+ }
1319
+ dialog.appendChild(title);
1320
+ dialog.appendChild(message);
1321
+
1322
+ return { overlay, dialog };
1323
+ }
1324
+
1325
+ function createDialogButton(label, onClick, margin = '8px 0') {
1326
+ const btn = document.createElement('button');
1327
+ btn.textContent = label;
1328
+ btn.style.display = 'block';
1329
+ btn.style.margin = margin;
1330
+ btn.onclick = onClick;
1331
+ return btn;
1332
+ }
1333
+
1334
+ function openCsvPicker(onComplete) {
1335
+ let csvInput = document.createElement('input');
1336
+ csvInput.type = 'file';
1337
+ csvInput.accept = '.csv,.CSV';
1338
+ csvInput.style.display = 'none';
1339
+ document.body.appendChild(csvInput);
1340
+ csvInput.addEventListener('change', function() {
1341
+ if (csvInput.files && csvInput.files.length === 1) {
1342
+ window.selectedKeyenceCsv = csvInput.files[0];
1343
+ logStatus(`CSV key file supplied: ${csvInput.files[0].name}`);
1344
+ } else {
1345
+ window.selectedKeyenceCsv = null;
1346
+ logStatus('No CSV key file supplied.');
1347
+ }
1348
+ document.body.removeChild(csvInput);
1349
+ if (onComplete) onComplete();
1350
+ });
1351
+ csvInput.click();
1352
+ }
1353
+
1354
+ function showCsvSelectionDialog(csvFiles) {
1355
+ const { overlay, dialog } = createDialogOverlay(
1356
+ 'Select a CSV key file',
1357
+ 'Multiple CSV files were found. Please select one to use as the key file:'
1358
+ );
1359
 
1360
  // List CSV files as buttons
1361
  csvFiles.forEach((file, idx) => {
1362
+ const btn = createDialogButton(file.name, () => {
 
 
 
 
1363
  window.selectedKeyenceCsv = file;
1364
  logStatus(`CSV key file selected: ${file.name}`);
1365
  document.body.removeChild(overlay);
1366
+ });
1367
  dialog.appendChild(btn);
1368
  });
1369
 
1370
  // Option: Use a different .csv
1371
+ const otherBtn = createDialogButton('Use a different .csv', () => {
1372
+ openCsvPicker(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1373
  document.body.removeChild(overlay);
1374
  });
1375
+ }, '16px 0 8px 0');
 
1376
  dialog.appendChild(otherBtn);
1377
 
1378
  // Option: No key file
1379
+ const noneBtn = createDialogButton('No key file', () => {
 
 
 
 
1380
  window.selectedKeyenceCsv = null;
1381
  logStatus('No CSV key file will be used.');
1382
  document.body.removeChild(overlay);
1383
+ });
1384
  dialog.appendChild(noneBtn);
1385
 
1386
  // Cancel button
1387
+ const cancelBtn = createDialogButton('Cancel', () => {
 
 
 
 
1388
  // Do not change selection, just close dialog
1389
  logStatus('CSV key file selection cancelled.');
1390
  document.body.removeChild(overlay);
1391
+ });
1392
  dialog.appendChild(cancelBtn);
1393
 
1394
  overlay.appendChild(dialog);
 
1397
 
1398
  // Custom dialog for single CSV file found
1399
  function showSingleCsvDialog(csvFile) {
1400
+ const messageFragment = document.createDocumentFragment();
1401
+ messageFragment.append('A CSV file named ');
1402
+ const fileNameStrong = document.createElement('strong');
1403
+ fileNameStrong.textContent = csvFile.name;
1404
+ messageFragment.appendChild(fileNameStrong);
1405
+ messageFragment.append(' was found in the folder. Would you like to use this as the key file for this folder?');
1406
+
1407
+ const { overlay, dialog } = createDialogOverlay(
1408
+ 'CSV File Found',
1409
+ messageFragment
1410
+ );
 
 
 
 
 
 
 
 
1411
 
1412
  // Yes button
1413
+ const yesBtn = createDialogButton('Yes', () => {
 
 
 
 
1414
  window.selectedKeyenceCsv = csvFile;
1415
  logStatus(`CSV key file selected: ${csvFile.name}`);
1416
  document.body.removeChild(overlay);
1417
+ });
1418
  dialog.appendChild(yesBtn);
1419
 
1420
  // Use a different .csv file
1421
+ const otherBtn = createDialogButton('Use a different .csv file', () => {
1422
+ openCsvPicker(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1423
  document.body.removeChild(overlay);
1424
  });
1425
+ });
 
1426
  dialog.appendChild(otherBtn);
1427
 
1428
  // Do not use a key file
1429
+ const noneBtn = createDialogButton('Do not use a key file', () => {
 
 
 
 
1430
  window.selectedKeyenceCsv = null;
1431
  logStatus('No CSV key file will be used.');
1432
  document.body.removeChild(overlay);
1433
+ });
1434
  dialog.appendChild(noneBtn);
1435
 
1436
  // Cancel button
1437
+ const cancelBtn = createDialogButton('Cancel', () => {
 
 
 
 
1438
  // Do not change selection, just close dialog
1439
  logStatus('CSV key file selection cancelled.');
1440
  document.body.removeChild(overlay);
1441
+ });
1442
+ dialog.appendChild(cancelBtn);
1443
+
1444
+ overlay.appendChild(dialog);
1445
+ document.body.appendChild(overlay);
1446
+ }
1447
+
1448
+ // Custom dialog for no CSV file found
1449
+ function showNoCsvDialog() {
1450
+ const { overlay, dialog } = createDialogOverlay(
1451
+ 'No CSV Key File Found',
1452
+ 'No CSV key file was found in the folder. Would you like to supply one now?'
1453
+ );
1454
+
1455
+ const uploadBtn = createDialogButton('Upload a CSV key file', () => {
1456
+ openCsvPicker(() => {
1457
+ document.body.removeChild(overlay);
1458
+ });
1459
+ });
1460
+ dialog.appendChild(uploadBtn);
1461
+
1462
+ const noneBtn = createDialogButton('No key file', () => {
1463
+ window.selectedKeyenceCsv = null;
1464
+ logStatus('No CSV key file will be used.');
1465
+ document.body.removeChild(overlay);
1466
+ });
1467
+ dialog.appendChild(noneBtn);
1468
+
1469
+ const cancelBtn = createDialogButton('Cancel', () => {
1470
+ logStatus('CSV key file selection cancelled.');
1471
+ document.body.removeChild(overlay);
1472
+ });
1473
  dialog.appendChild(cancelBtn);
1474
 
1475
  overlay.appendChild(dialog);
 
1488
  confidenceSlider.value = 0.6;
1489
  confidenceSlider.dispatchEvent(new Event('input', { bubbles: true }));
1490
  confidenceSlider.dispatchEvent(new Event('change', { bubbles: true }));
1491
+ });