data-preprocessing-step.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. "use client"
  2. import { useState } from "react"
  3. import { Button } from "@/components/ui/button"
  4. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
  5. import { Badge } from "@/components/ui/badge"
  6. import { Progress } from "@/components/ui/progress"
  7. import { Slider } from "@/components/ui/slider"
  8. import { Switch } from "@/components/ui/switch"
  9. import { Label } from "@/components/ui/label"
  10. import {
  11. RotateCw,
  12. FlipVerticalIcon as Flip,
  13. Contrast,
  14. Sun,
  15. Crop,
  16. Palette,
  17. Info,
  18. Play,
  19. Pause,
  20. SkipForward,
  21. CheckCircle,
  22. AlertCircle,
  23. } from "lucide-react"
  24. interface PreprocessingSettings {
  25. resize: boolean
  26. targetSize: number
  27. normalize: boolean
  28. augmentation: boolean
  29. rotation: number
  30. brightness: number
  31. contrast: number
  32. flip: boolean
  33. crop: boolean
  34. }
  35. interface ProcessingStep {
  36. id: string
  37. name: string
  38. description: string
  39. icon: any
  40. status: "pending" | "processing" | "completed" | "error"
  41. progress: number
  42. }
  43. const defaultSampleImages = [
  44. { id: "1", name: "风景画1.jpg", url: "/mountain-lake-painting.png" },
  45. { id: "2", name: "抽象艺术.jpg", url: "/colorful-geometric-abstract.png" },
  46. { id: "3", name: "人物肖像.jpg", url: "/artistic-portrait.png" },
  47. { id: "4", name: "花卉静物.jpg", url: "/floral-still-life.png" },
  48. ]
  49. interface DataPreprocessingStepProps {
  50. onNext?: () => void
  51. selectedImages?: string[]
  52. }
  53. export default function DataPreprocessingStep({ onNext, selectedImages = [] }: DataPreprocessingStepProps) {
  54. const [settings, setSettings] = useState<PreprocessingSettings>({
  55. resize: true,
  56. targetSize: 512,
  57. normalize: true,
  58. augmentation: true,
  59. rotation: 15,
  60. brightness: 10,
  61. contrast: 10,
  62. flip: true,
  63. crop: false,
  64. })
  65. const [processingSteps, setProcessingSteps] = useState<ProcessingStep[]>([
  66. {
  67. id: "quality-check",
  68. name: "质量检查",
  69. description: "检查图像分辨率、格式和完整性",
  70. icon: AlertCircle,
  71. status: "pending",
  72. progress: 0,
  73. },
  74. {
  75. id: "resize",
  76. name: "尺寸标准化",
  77. description: "将所有图像调整为统一尺寸",
  78. icon: Crop,
  79. status: "pending",
  80. progress: 0,
  81. },
  82. {
  83. id: "normalize",
  84. name: "数值归一化",
  85. description: "将像素值标准化到0-1范围",
  86. icon: Palette,
  87. status: "pending",
  88. progress: 0,
  89. },
  90. {
  91. id: "augmentation",
  92. name: "数据增强",
  93. description: "通过旋转、翻转等方式增加数据多样性",
  94. icon: RotateCw,
  95. status: "pending",
  96. progress: 0,
  97. },
  98. ])
  99. const [isProcessing, setIsProcessing] = useState(false)
  100. const [currentProcessingStep, setCurrentProcessingStep] = useState(0)
  101. const sampleImages =
  102. selectedImages.length > 0
  103. ? selectedImages.slice(0, 4).map((url, index) => ({
  104. id: `selected-${index}`,
  105. name: `选中图像${index + 1}.jpg`,
  106. url: url,
  107. }))
  108. : defaultSampleImages
  109. const [selectedImage, setSelectedImage] = useState(sampleImages[0])
  110. const updateSetting = (key: keyof PreprocessingSettings, value: any) => {
  111. setSettings((prev) => ({ ...prev, [key]: value }))
  112. }
  113. const startProcessing = async () => {
  114. setIsProcessing(true)
  115. setCurrentProcessingStep(0)
  116. // Reset all steps
  117. setProcessingSteps((prev) =>
  118. prev.map((step) => ({
  119. ...step,
  120. status: "pending" as const,
  121. progress: 0,
  122. })),
  123. )
  124. // Simulate processing each step
  125. for (let i = 0; i < processingSteps.length; i++) {
  126. setCurrentProcessingStep(i)
  127. // Update current step to processing
  128. setProcessingSteps((prev) =>
  129. prev.map((step, index) => (index === i ? { ...step, status: "processing" as const } : step)),
  130. )
  131. // Simulate progress
  132. for (let progress = 0; progress <= 100; progress += 10) {
  133. await new Promise((resolve) => setTimeout(resolve, 100))
  134. setProcessingSteps((prev) => prev.map((step, index) => (index === i ? { ...step, progress } : step)))
  135. }
  136. // Mark as completed
  137. setProcessingSteps((prev) =>
  138. prev.map((step, index) => (index === i ? { ...step, status: "completed" as const, progress: 100 } : step)),
  139. )
  140. }
  141. setIsProcessing(false)
  142. }
  143. const getImageTransform = () => {
  144. let transform = ""
  145. if (settings.rotation > 0) {
  146. transform += `rotate(${settings.rotation}deg) `
  147. }
  148. if (settings.flip) {
  149. transform += "scaleX(-1) "
  150. }
  151. return transform
  152. }
  153. const getImageFilter = () => {
  154. let filter = ""
  155. if (settings.brightness !== 0) {
  156. filter += `brightness(${100 + settings.brightness}%) `
  157. }
  158. if (settings.contrast !== 0) {
  159. filter += `contrast(${100 + settings.contrast}%) `
  160. }
  161. return filter
  162. }
  163. const completedSteps = processingSteps.filter((step) => step.status === "completed").length
  164. const totalSteps = processingSteps.length
  165. const handleNext = () => {
  166. if (onNext && completedSteps >= totalSteps) {
  167. onNext()
  168. }
  169. }
  170. return (
  171. <div className="space-y-6">
  172. {/* Introduction */}
  173. <Card className="bg-secondary/5 border-secondary/20">
  174. <CardHeader>
  175. <div className="flex items-center gap-2">
  176. <Info className="w-5 h-5 text-secondary" />
  177. <CardTitle className="text-lg font-serif">什么是数据预处理?</CardTitle>
  178. </div>
  179. </CardHeader>
  180. <CardContent>
  181. <p className="text-muted-foreground leading-relaxed">
  182. 数据预处理是确保AI模型能够有效学习的关键步骤。就像厨师在烹饪前需要清洗和切配食材一样,
  183. 我们需要对图像数据进行标准化处理,包括调整尺寸、增强数据多样性、检查质量等, 以提高模型训练的效果和稳定性。
  184. </p>
  185. </CardContent>
  186. </Card>
  187. <div className="grid lg:grid-cols-2 gap-6">
  188. {/* Settings Panel */}
  189. <Card>
  190. <CardHeader>
  191. <CardTitle className="font-serif">预处理设置</CardTitle>
  192. <CardDescription>调整预处理参数,观察对图像的影响</CardDescription>
  193. </CardHeader>
  194. <CardContent className="space-y-6">
  195. {/* Image Resize */}
  196. <div className="space-y-3">
  197. <div className="flex items-center justify-between">
  198. <Label htmlFor="resize" className="font-medium">
  199. 尺寸标准化
  200. </Label>
  201. <Switch
  202. id="resize"
  203. checked={settings.resize}
  204. onCheckedChange={(checked) => updateSetting("resize", checked)}
  205. />
  206. </div>
  207. {settings.resize && (
  208. <div className="space-y-2">
  209. <Label className="text-sm text-muted-foreground">目标尺寸: {settings.targetSize}px</Label>
  210. <Slider
  211. value={[settings.targetSize]}
  212. onValueChange={([value]) => updateSetting("targetSize", value)}
  213. min={256}
  214. max={1024}
  215. step={64}
  216. className="w-full"
  217. />
  218. </div>
  219. )}
  220. </div>
  221. {/* Normalization */}
  222. <div className="flex items-center justify-between">
  223. <div>
  224. <Label htmlFor="normalize" className="font-medium">
  225. 数值归一化
  226. </Label>
  227. <p className="text-xs text-muted-foreground">将像素值标准化到0-1范围</p>
  228. </div>
  229. <Switch
  230. id="normalize"
  231. checked={settings.normalize}
  232. onCheckedChange={(checked) => updateSetting("normalize", checked)}
  233. />
  234. </div>
  235. {/* Data Augmentation */}
  236. <div className="space-y-3">
  237. <div className="flex items-center justify-between">
  238. <Label htmlFor="augmentation" className="font-medium">
  239. 数据增强
  240. </Label>
  241. <Switch
  242. id="augmentation"
  243. checked={settings.augmentation}
  244. onCheckedChange={(checked) => updateSetting("augmentation", checked)}
  245. />
  246. </div>
  247. {settings.augmentation && (
  248. <div className="space-y-4 pl-4 border-l-2 border-muted">
  249. {/* Rotation */}
  250. <div className="space-y-2">
  251. <Label className="text-sm flex items-center gap-2">
  252. <RotateCw className="w-4 h-4" />
  253. 旋转角度: ±{settings.rotation}°
  254. </Label>
  255. <Slider
  256. value={[settings.rotation]}
  257. onValueChange={([value]) => updateSetting("rotation", value)}
  258. min={0}
  259. max={45}
  260. step={5}
  261. className="w-full"
  262. />
  263. </div>
  264. {/* Brightness */}
  265. <div className="space-y-2">
  266. <Label className="text-sm flex items-center gap-2">
  267. <Sun className="w-4 h-4" />
  268. 亮度调整: ±{settings.brightness}%
  269. </Label>
  270. <Slider
  271. value={[settings.brightness]}
  272. onValueChange={([value]) => updateSetting("brightness", value)}
  273. min={0}
  274. max={30}
  275. step={5}
  276. className="w-full"
  277. />
  278. </div>
  279. {/* Contrast */}
  280. <div className="space-y-2">
  281. <Label className="text-sm flex items-center gap-2">
  282. <Contrast className="w-4 h-4" />
  283. 对比度调整: ±{settings.contrast}%
  284. </Label>
  285. <Slider
  286. value={[settings.contrast]}
  287. onValueChange={([value]) => updateSetting("contrast", value)}
  288. min={0}
  289. max={30}
  290. step={5}
  291. className="w-full"
  292. />
  293. </div>
  294. {/* Flip */}
  295. <div className="flex items-center justify-between">
  296. <Label htmlFor="flip" className="text-sm flex items-center gap-2">
  297. <Flip className="w-4 h-4" />
  298. 水平翻转
  299. </Label>
  300. <Switch
  301. id="flip"
  302. checked={settings.flip}
  303. onCheckedChange={(checked) => updateSetting("flip", checked)}
  304. />
  305. </div>
  306. </div>
  307. )}
  308. </div>
  309. </CardContent>
  310. </Card>
  311. {/* Preview Panel */}
  312. <Card>
  313. <CardHeader>
  314. <CardTitle className="font-serif">预处理预览</CardTitle>
  315. <CardDescription>
  316. {selectedImages.length > 0 ? "使用你选择的图像进行预处理预览" : "使用示例图像进行预处理预览"}
  317. </CardDescription>
  318. </CardHeader>
  319. <CardContent>
  320. <div className="space-y-4">
  321. {/* Image Selector */}
  322. <div className="flex gap-2 overflow-x-auto pb-2">
  323. {sampleImages.map((image) => (
  324. <button
  325. key={image.id}
  326. onClick={() => setSelectedImage(image)}
  327. className={`flex-shrink-0 w-16 h-16 rounded-lg overflow-hidden border-2 transition-colors ${
  328. selectedImage.id === image.id ? "border-primary" : "border-border"
  329. }`}
  330. >
  331. <img
  332. src={image.url || "/placeholder.svg"}
  333. alt={image.name}
  334. className="w-full h-full object-cover"
  335. />
  336. </button>
  337. ))}
  338. </div>
  339. {/* Before/After Comparison */}
  340. <div className="grid grid-cols-2 gap-4">
  341. <div>
  342. <Label className="text-sm font-medium mb-2 block">原始图像</Label>
  343. <div className="aspect-square bg-muted rounded-lg overflow-hidden">
  344. <img
  345. src={selectedImage.url || "/placeholder.svg"}
  346. alt="原始图像"
  347. className="w-full h-full object-cover"
  348. />
  349. </div>
  350. </div>
  351. <div>
  352. <Label className="text-sm font-medium mb-2 block">预处理后</Label>
  353. <div className="aspect-square bg-muted rounded-lg overflow-hidden">
  354. <img
  355. src={selectedImage.url || "/placeholder.svg"}
  356. alt="预处理后"
  357. className="w-full h-full object-cover transition-all duration-300"
  358. style={{
  359. transform: getImageTransform(),
  360. filter: getImageFilter(),
  361. width: settings.resize ? `${Math.min(100, (settings.targetSize / 512) * 100)}%` : "100%",
  362. height: settings.resize ? `${Math.min(100, (settings.targetSize / 512) * 100)}%` : "100%",
  363. }}
  364. />
  365. </div>
  366. </div>
  367. </div>
  368. {/* Processing Info */}
  369. <div className="bg-muted/50 rounded-lg p-4 space-y-2">
  370. <h4 className="font-medium text-sm">预处理信息</h4>
  371. <div className="text-xs text-muted-foreground space-y-1">
  372. {settings.resize && (
  373. <p>
  374. • 尺寸: {settings.targetSize}×{settings.targetSize}px
  375. </p>
  376. )}
  377. {settings.normalize && <p>• 像素值归一化: 0-1范围</p>}
  378. {settings.augmentation && (
  379. <>
  380. {settings.rotation > 0 && <p>• 随机旋转: ±{settings.rotation}°</p>}
  381. {settings.brightness > 0 && <p>• 亮度变化: ±{settings.brightness}%</p>}
  382. {settings.contrast > 0 && <p>• 对比度变化: ±{settings.contrast}%</p>}
  383. {settings.flip && <p>• 随机水平翻转</p>}
  384. </>
  385. )}
  386. </div>
  387. </div>
  388. </div>
  389. </CardContent>
  390. </Card>
  391. </div>
  392. {/* Processing Pipeline */}
  393. <Card>
  394. <CardHeader>
  395. <div className="flex items-center justify-between">
  396. <div>
  397. <CardTitle className="font-serif">预处理流水线</CardTitle>
  398. <CardDescription>模拟数据预处理的完整流程</CardDescription>
  399. </div>
  400. <Button onClick={startProcessing} disabled={isProcessing} className="font-semibold">
  401. {isProcessing ? (
  402. <>
  403. <Pause className="w-4 h-4 mr-2" />
  404. 处理中...
  405. </>
  406. ) : (
  407. <>
  408. <Play className="w-4 h-4 mr-2" />
  409. 开始预处理
  410. </>
  411. )}
  412. </Button>
  413. </div>
  414. </CardHeader>
  415. <CardContent>
  416. <div className="space-y-4">
  417. {/* Overall Progress */}
  418. <div className="space-y-2">
  419. <div className="flex justify-between text-sm">
  420. <span>总体进度</span>
  421. <span>
  422. {completedSteps}/{totalSteps} 步骤完成
  423. </span>
  424. </div>
  425. <Progress value={(completedSteps / totalSteps) * 100} className="h-2" />
  426. </div>
  427. {/* Processing Steps */}
  428. <div className="space-y-3">
  429. {processingSteps.map((step, index) => {
  430. const Icon = step.icon
  431. return (
  432. <div
  433. key={step.id}
  434. className={`flex items-center gap-4 p-4 rounded-lg border transition-all ${
  435. step.status === "processing"
  436. ? "border-primary bg-primary/5"
  437. : step.status === "completed"
  438. ? "border-secondary bg-secondary/5"
  439. : "border-border"
  440. }`}
  441. >
  442. <div
  443. className={`p-2 rounded-md ${
  444. step.status === "processing"
  445. ? "bg-primary text-primary-foreground"
  446. : step.status === "completed"
  447. ? "bg-secondary text-secondary-foreground"
  448. : "bg-muted text-muted-foreground"
  449. }`}
  450. >
  451. {step.status === "completed" ? <CheckCircle className="w-5 h-5" /> : <Icon className="w-5 h-5" />}
  452. </div>
  453. <div className="flex-1">
  454. <h4 className="font-medium">{step.name}</h4>
  455. <p className="text-sm text-muted-foreground">{step.description}</p>
  456. {step.status === "processing" && (
  457. <div className="mt-2">
  458. <Progress value={step.progress} className="h-1" />
  459. </div>
  460. )}
  461. </div>
  462. <Badge
  463. variant={
  464. step.status === "completed" ? "default" : step.status === "processing" ? "secondary" : "outline"
  465. }
  466. >
  467. {step.status === "completed" ? "已完成" : step.status === "processing" ? "处理中" : "等待中"}
  468. </Badge>
  469. </div>
  470. )
  471. })}
  472. </div>
  473. </div>
  474. </CardContent>
  475. </Card>
  476. {/* Next Step Button */}
  477. <div className="flex justify-end">
  478. <Button size="lg" disabled={completedSteps < totalSteps} className="font-semibold" onClick={handleNext}>
  479. 继续到模型训练
  480. <SkipForward className="w-4 h-4 ml-2" />
  481. </Button>
  482. </div>
  483. </div>
  484. )
  485. }