Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | import fs from 'fs' import path from 'path' import { withAuth } from '@/lib/auth/withAuth' // Force dynamic rendering - this route reads from disk which changes at runtime export const dynamic = 'force-dynamic' // Data directories for each model type const COLUMN_CLASSIFIER_DIR = path.join(process.cwd(), 'data/vision-training/collected') const BOUNDARY_DETECTOR_DIR = path.join(process.cwd(), 'data/vision-training/boundary-frames') type DataQuality = 'none' | 'insufficient' | 'minimal' | 'good' | 'excellent' interface ModelSummary { totalImages?: number totalFrames?: number hasData: boolean dataQuality: DataQuality } interface ModelsSummaryResponse { columnClassifier: ModelSummary & { totalImages: number } boundaryDetector: ModelSummary & { totalFrames: number } } /** * Calculate data quality based on count and minimum requirements */ function calculateColumnClassifierQuality(totalImages: number, digitCounts: number[]): DataQuality { if (totalImages === 0) return 'none' const minCount = Math.min(...digitCounts) const avgCount = totalImages / 10 if (totalImages < 50 || minCount < 3) return 'insufficient' if (totalImages < 200 || minCount < 10) return 'minimal' if (totalImages < 500 || avgCount < 40) return 'good' return 'excellent' } function calculateBoundaryDetectorQuality(totalFrames: number): DataQuality { if (totalFrames === 0) return 'none' if (totalFrames < 50) return 'insufficient' if (totalFrames < 200) return 'minimal' if (totalFrames < 500) return 'good' return 'excellent' } /** * GET /api/vision-training/models-summary * * Returns a summary of available training data for all model types. * Used by the model selection card in the training wizard. */ export const GET = withAuth( async () => { try { // --- Column Classifier Summary --- let columnTotalImages = 0 const digitCounts: number[] = [] if (fs.existsSync(COLUMN_CLASSIFIER_DIR)) { for (let d = 0; d <= 9; d++) { const digitDir = path.join(COLUMN_CLASSIFIER_DIR, String(d)) let count = 0 if (fs.existsSync(digitDir)) { const files = fs.readdirSync(digitDir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f)) count = files.length } digitCounts.push(count) columnTotalImages += count } } else { // Initialize with zeros for all digits for (let i = 0; i < 10; i++) { digitCounts.push(0) } } const columnClassifier: ModelsSummaryResponse['columnClassifier'] = { totalImages: columnTotalImages, hasData: columnTotalImages > 0, dataQuality: calculateColumnClassifierQuality(columnTotalImages, digitCounts), } // --- Boundary Detector Summary --- let boundaryTotalFrames = 0 if (fs.existsSync(BOUNDARY_DETECTOR_DIR)) { // Count all frame images (organized by device subdirectories) const entries = fs.readdirSync(BOUNDARY_DETECTOR_DIR, { withFileTypes: true, }) for (const entry of entries) { if (entry.isDirectory()) { // Device subdirectory const deviceDir = path.join(BOUNDARY_DETECTOR_DIR, entry.name) const files = fs.readdirSync(deviceDir).filter((f) => /\.(png|jpg|jpeg|webp)$/i.test(f)) boundaryTotalFrames += files.length } else if (/\.(png|jpg|jpeg|webp)$/i.test(entry.name)) { // Direct file in boundary-frames directory boundaryTotalFrames++ } } } const boundaryDetector: ModelsSummaryResponse['boundaryDetector'] = { totalFrames: boundaryTotalFrames, hasData: boundaryTotalFrames > 0, dataQuality: calculateBoundaryDetectorQuality(boundaryTotalFrames), } return Response.json({ columnClassifier, boundaryDetector, } satisfies ModelsSummaryResponse) } catch (error) { console.error('[vision-training/models-summary] Error:', error) return Response.json({ error: 'Failed to read model summaries' }, { status: 500 }) } }, { role: 'admin' } ) |