malavikapradeep2001 commited on
Commit
3e369f2
·
1 Parent(s): bf5da6b

Initial Space

Browse files
frontend/src/App.tsx CHANGED
@@ -103,12 +103,10 @@ return ( <div className="flex flex-col min-h-screen w-full bg-gray-50"> <Header
103
  onAnalyze={handleAnalyze}
104
  />
105
 
106
- {/* Results Panel */}
107
- {showResults && (
108
  <ResultsPanel
109
- uploadedImage={
110
- apiResult?.annotated_image_url || uploadedImage
111
- }
112
  result={apiResult}
113
  loading={loading}
114
  />
 
103
  onAnalyze={handleAnalyze}
104
  />
105
 
106
+ {/* Results Panel (show while loading OR when results ready) */}
107
+ {(loading || showResults) && (
108
  <ResultsPanel
109
+ uploadedImage={apiResult?.annotated_image_url || uploadedImage}
 
 
110
  result={apiResult}
111
  loading={loading}
112
  />
frontend/src/components/Header.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  export function Header() {
2
  return (
3
  <div className="w-full bg-white">
@@ -17,9 +18,11 @@ export function Header() {
17
  alt="Manalife Logo"
18
  className="h-16 w-auto"
19
  />
20
- <h1 className="text-white text-2xl font-semibold tracking-wide">
21
- Manalife AI Pathology Assistant
22
- </h1>
 
 
23
  </div>
24
  </div>
25
  </div>
 
1
+
2
  export function Header() {
3
  return (
4
  <div className="w-full bg-white">
 
18
  alt="Manalife Logo"
19
  className="h-16 w-auto"
20
  />
21
+ <h1 className="text-white text-3xl font-bold">Pathora</h1>
22
+ <div className="h-8 border-l border-white/70" />
23
+ <h3 className="text-white text-1xl font-semibold tracking-wide">
24
+ Manalife's AI Pathology Assistant
25
+ </h3>
26
  </div>
27
  </div>
28
  </div>
frontend/src/components/ResultsPanel.tsx CHANGED
@@ -1,143 +1,183 @@
 
1
  import { DownloadIcon, InfoIcon, Loader2Icon } from "lucide-react";
2
 
3
  interface ResultsPanelProps {
4
- uploadedImage: string | null;
5
- result?: any;
6
- loading?: boolean;
7
  }
8
 
9
  export function ResultsPanel({ uploadedImage, result, loading }: ResultsPanelProps) {
10
- if (loading) {
11
- return ( <div className="bg-white rounded-lg shadow-sm p-6 flex flex-col items-center justify-center"> <Loader2Icon className="w-10 h-10 text-blue-600 animate-spin mb-3" /> <p className="text-gray-600 font-medium">Analyzing image...</p> </div>
12
- );
13
- }
14
-
15
- if (!result) {
16
- return ( <div className="bg-white rounded-lg shadow-sm p-6 text-center text-gray-500">
17
- No analysis result available yet. </div>
18
- );
19
- }
20
-
21
- const {
22
- prediction,
23
- confidence,
24
- probabilities,
25
- detections,
26
- summary,
27
- annotated_image_url,
28
- model_name,
29
- analysis_type,
30
- } = result;
31
-
32
- const handleDownload = () => {
33
- if (annotated_image_url) {
34
- const link = document.createElement("a");
35
- link.href = annotated_image_url;
36
- link.download = "analysis_result.jpg";
37
- link.click();
38
- }
39
- };
40
-
41
- return ( <div className="bg-white rounded-lg shadow-sm p-6">
42
- {/* Header */} <div className="flex items-center justify-between mb-6"> <div> <h2 className="text-2xl font-bold text-gray-800">
43
- {model_name ? model_name.toUpperCase() : "Analysis Result"} </h2> <p className="text-sm text-gray-500 capitalize">
44
- {analysis_type || "Test Type"} </p> </div>
45
- {annotated_image_url && ( <button
46
- onClick={handleDownload}
47
- className="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors"
48
- > <DownloadIcon className="w-4 h-4" />
49
- Download Image </button>
50
- )} </div>
51
-
52
-
53
- {/* Image */}
54
- <div className="relative mb-6 rounded-lg overflow-hidden border border-gray-200">
55
- <img
56
- src={annotated_image_url || uploadedImage || "/ui.jpg"}
57
- alt="Analysis Result"
58
- className="w-full h-64 object-cover"
59
- />
60
- </div>
61
-
62
- {/* Results Summary */}
63
- <div className="mb-6">
64
- {prediction && (
65
- <h3
66
- className={`text-3xl font-bold ${
67
- prediction.toLowerCase().includes("malignant") ||
68
- prediction.toLowerCase().includes("abnormal")
69
- ? "text-red-600"
70
- : "text-green-600"
71
- }`}
72
- >
73
- {prediction}
74
- </h3>
75
- )}
76
-
77
- {confidence && (
78
- <div className="mt-2">
79
- <div className="flex items-center justify-between mb-1">
80
- <span className="font-semibold text-gray-900">
81
- Confidence: {(confidence * 100).toFixed(2)}%
82
- </span>
83
- <InfoIcon className="w-4 h-4 text-gray-400" />
84
  </div>
85
- <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden">
86
- <div
87
- className={`h-full ${
88
- confidence > 0.7
89
- ? "bg-green-500"
90
- : confidence > 0.4
91
- ? "bg-yellow-500"
92
- : "bg-red-500"
93
- }`}
94
- style={{ width: `${confidence * 100}%` }}
95
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  </div>
 
 
 
 
 
 
 
 
 
 
97
  </div>
98
- )}
99
-
100
- {summary && (
101
- <p className="mt-4 text-gray-700 text-sm leading-relaxed">
102
- {summary}
103
- </p>
104
- )}
105
- </div>
106
-
107
- {/* Detections / Probabilities */}
108
- {detections && detections.length > 0 && (
109
- <div className="mb-6">
110
- <h4 className="font-semibold text-gray-900 mb-3">
111
- Detected Regions:
112
- </h4>
113
- <ul className="text-sm text-gray-700 list-disc list-inside space-y-1">
114
- {detections.map((det: any, i: number) => (
115
- <li key={i}>
116
- {det.name || "object"} – {(det.confidence * 100).toFixed(1)}%
117
- </li>
118
- ))}
119
- </ul>
120
- </div>
121
- )}
122
-
123
- {probabilities && (
124
- <div className="mb-6">
125
- <h4 className="font-semibold text-gray-900 mb-3">
126
- Class Probabilities:
127
- </h4>
128
- <pre className="bg-gray-100 rounded-lg p-3 text-sm">
129
- {JSON.stringify(probabilities, null, 2)}
130
- </pre>
131
- </div>
132
- )}
133
 
134
- {/* Report Button */}
135
- <button className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors flex items-center justify-center gap-2">
136
- <DownloadIcon className="w-5 h-5" />
137
- Generate Report
138
- </button>
139
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  }
 
1
+ import { useState, useEffect } from "react";
2
  import { DownloadIcon, InfoIcon, Loader2Icon } from "lucide-react";
3
 
4
  interface ResultsPanelProps {
5
+ uploadedImage: string | null;
6
+ result?: any;
7
+ loading?: boolean;
8
  }
9
 
10
  export function ResultsPanel({ uploadedImage, result, loading }: ResultsPanelProps) {
11
+ const [isModalOpen, setIsModalOpen] = useState(false);
12
+ const [modalImage, setModalImage] = useState<string | null>(null);
13
+
14
+ useEffect(() => {
15
+ if (result?.annotated_image_url) {
16
+ setModalImage(result.annotated_image_url);
17
+ } else if (uploadedImage) {
18
+ setModalImage(uploadedImage);
19
+ } else {
20
+ setModalImage(null);
21
+ }
22
+ }, [result, uploadedImage]);
23
+
24
+ // Loading state UI
25
+ if (loading) {
26
+ return (
27
+ <div className="bg-white rounded-lg shadow-sm p-8 flex flex-col items-center justify-center">
28
+ <Loader2Icon className="w-12 h-12 text-transparent bg-clip-text bg-gradient-to-r from-blue-800 to-teal-600 animate-spin mb-4" />
29
+ <p className="text-gray-700 font-medium mb-2">Analyzing image... Please wait</p>
30
+ <div className="w-56 h-2 rounded-full overflow-hidden mt-2">
31
+ <div className="h-full bg-gradient-to-r from-blue-800 to-teal-600 animate-pulse" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  </div>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ if (!result) {
38
+ return (
39
+ <div className="bg-white rounded-lg shadow-sm p-6 text-center text-gray-500">
40
+ No analysis result available yet.
41
+ </div>
42
+ );
43
+ }
44
+
45
+ const {
46
+ prediction,
47
+ confidence,
48
+ probabilities,
49
+ detections,
50
+ summary,
51
+ annotated_image_url,
52
+ model_name,
53
+ analysis_type,
54
+ } = result;
55
+
56
+ const handleDownload = () => {
57
+ if (annotated_image_url) {
58
+ const link = document.createElement("a");
59
+ link.href = annotated_image_url;
60
+ link.download = "analysis_result.jpg";
61
+ link.click();
62
+ }
63
+ };
64
+
65
+ const openModal = (url?: string) => {
66
+ setModalImage(url || annotated_image_url || uploadedImage);
67
+ setIsModalOpen(true);
68
+ };
69
+
70
+ const closeModal = () => {
71
+ setIsModalOpen(false);
72
+ };
73
+
74
+ return (
75
+ <div className="bg-white rounded-lg shadow-sm p-6">
76
+ {/* Header */}
77
+ <div className="flex items-center justify-between mb-6">
78
+ <div>
79
+ <h2 className="text-2xl font-bold text-gray-800">
80
+ {model_name ? model_name.toUpperCase() : "Analysis Result"}
81
+ </h2>
82
+ <p className="text-sm text-gray-500 capitalize">{analysis_type || "Test Type"}</p>
83
  </div>
84
+
85
+ {annotated_image_url && (
86
+ <button
87
+ onClick={handleDownload}
88
+ className="flex items-center gap-2 bg-gradient-to-r from-blue-800 to-teal-600 text-white px-4 py-2 rounded-lg hover:opacity-90 transition-all"
89
+ >
90
+ <DownloadIcon className="w-4 h-4" />
91
+ Download Image
92
+ </button>
93
+ )}
94
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ {/* Image (click to zoom) */}
97
+ <div className="relative mb-6 rounded-lg overflow-hidden border border-gray-200">
98
+ <img
99
+ src={annotated_image_url || uploadedImage || "/ui.jpg"}
100
+ alt="Analysis Result"
101
+ className="w-full h-64 object-cover cursor-pointer transition-transform hover:scale-105"
102
+ onClick={() => openModal()}
103
+ />
104
+ </div>
105
+
106
+ {/* Results Summary */}
107
+ <div className="mb-6">
108
+ {prediction && (
109
+ <h3
110
+ className={`text-3xl font-bold ${
111
+ prediction.toLowerCase().includes("malignant") || prediction.toLowerCase().includes("abnormal")
112
+ ? "text-red-600"
113
+ : "text-green-600"
114
+ }`}
115
+ >
116
+ {prediction}
117
+ </h3>
118
+ )}
119
+
120
+ {confidence && (
121
+ <div className="mt-2">
122
+ <div className="flex items-center justify-between mb-1">
123
+ <span className="font-semibold text-gray-900">Confidence: {(confidence * 100).toFixed(2)}%</span>
124
+ <InfoIcon className="w-4 h-4 text-gray-400" />
125
+ </div>
126
+ <div className="w-full h-3 bg-gray-200 rounded-full overflow-hidden">
127
+ <div
128
+ className={`h-full ${
129
+ confidence > 0.7 ? "bg-green-500" : confidence > 0.4 ? "bg-yellow-500" : "bg-red-500"
130
+ }`}
131
+ style={{ width: `${confidence * 100}%` }}
132
+ />
133
+ </div>
134
+ </div>
135
+ )}
136
+
137
+ {summary && (
138
+ <p className="mt-4 text-gray-700 text-sm leading-relaxed">{summary}</p>
139
+ )}
140
+ </div>
141
 
142
+ {/* Detections / Probabilities */}
143
+ {detections && detections.length > 0 && (
144
+ <div className="mb-6">
145
+ <h4 className="font-semibold text-gray-900 mb-3">Detected Regions:</h4>
146
+ <ul className="text-sm text-gray-700 list-disc list-inside space-y-1">
147
+ {detections.map((det: any, i: number) => (
148
+ <li key={i}>{det.name || "object"} – {(det.confidence * 100).toFixed(1)}%</li>
149
+ ))}
150
+ </ul>
151
+ </div>
152
+ )}
153
 
154
+ {probabilities && (
155
+ <div className="mb-6">
156
+ <h4 className="font-semibold text-gray-900 mb-3">Class Probabilities:</h4>
157
+ <pre className="bg-gray-100 rounded-lg p-3 text-sm">{JSON.stringify(probabilities, null, 2)}</pre>
158
+ </div>
159
+ )}
160
+
161
+ {/* Report Button */}
162
+ <button className="w-full bg-gradient-to-r from-blue-800 to-teal-600 text-white py-3 rounded-lg font-medium hover:opacity-95 transition-colors flex items-center justify-center gap-2">
163
+ <DownloadIcon className="w-5 h-5" />
164
+ Generate Report
165
+ </button>
166
+
167
+ {/* Modal for zoom */}
168
+ {isModalOpen && modalImage && (
169
+ <div
170
+ className="fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4"
171
+ onClick={closeModal}
172
+ >
173
+ <div className="max-w-5xl max-h-[90vh] w-full" onClick={(e) => e.stopPropagation()}>
174
+ <img src={modalImage} alt="Zoomed result" className="w-full h-auto object-contain rounded-lg shadow-2xl" />
175
+ <div className="mt-3 text-right">
176
+ <button onClick={closeModal} className="px-4 py-2 bg-white rounded-md">Close</button>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ )}
181
+ </div>
182
+ );
183
  }
frontend/src/components/Sidebar.tsx CHANGED
@@ -1,54 +1,62 @@
1
- import React, { useState } from 'react';
2
- import { ChevronDownIcon, FileTextIcon, HelpCircleIcon } from 'lucide-react';
3
  interface SidebarProps {
4
  selectedTest: string;
5
  onTestChange: (test: string) => void;
6
  }
7
- export function Sidebar({
8
- selectedTest,
9
- onTestChange
10
- }: SidebarProps) {
11
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
12
- const testTypes = [{
13
- value: 'cytology',
14
- label: 'Cytology Analysis'
15
- }, {
16
- value: 'colposcopy',
17
- label: 'Colposcopy Analysis'
18
- }, {
19
- value: 'histopathology',
20
- label: 'Histopathology Analysis'
21
- }];
22
- return <aside className="w-64 bg-white border-r border-gray-200 p-4">
23
- <div className="space-y-2">
24
- {/* New Test Dropdown */}
25
- <div className="relative">
26
- <button onClick={() => setIsDropdownOpen(!isDropdownOpen)} className="w-full bg-blue-600 text-white rounded-lg px-4 py-3 flex items-center justify-between hover:bg-blue-700 transition-colors">
27
- <div className="flex items-center gap-2">
28
- <div className="w-2 h-2 bg-white rounded-full" />
29
- <span className="font-medium">New Test</span>
30
- </div>
31
- <ChevronDownIcon className={`w-5 h-5 transition-transform ${isDropdownOpen ? 'rotate-180' : ''}`} />
32
- </button>
33
- {isDropdownOpen && <div className="absolute top-full left-0 right-0 mt-2 bg-white border border-gray-200 rounded-lg shadow-lg z-10">
34
- {testTypes.map(test => <button key={test.value} onClick={() => {
35
- onTestChange(test.value);
36
- setIsDropdownOpen(false);
37
- }} className={`w-full text-left px-4 py-3 hover:bg-gray-50 transition-colors ${selectedTest === test.value ? 'bg-blue-50 text-blue-600' : 'text-gray-700'}`}>
38
- {test.label}
39
- </button>)}
40
- </div>}
 
 
 
 
 
41
  </div>
 
42
  {/* History */}
43
  <button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
44
  <FileTextIcon className="w-5 h-5" />
45
  <span>History</span>
46
  </button>
 
47
  {/* Help */}
48
  <button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
49
  <HelpCircleIcon className="w-5 h-5" />
50
  <span>Help</span>
51
  </button>
52
  </div>
53
- </aside>;
 
54
  }
 
1
+ import { FileTextIcon, HelpCircleIcon } from 'lucide-react';
2
+
3
  interface SidebarProps {
4
  selectedTest: string;
5
  onTestChange: (test: string) => void;
6
  }
7
+
8
+ export function Sidebar({ selectedTest, onTestChange }: SidebarProps) {
9
+ const testTypes = [
10
+ { value: 'cytology', label: 'Cytology Analysis' },
11
+ { value: 'colposcopy', label: 'Colposcopy Analysis' },
12
+ { value: 'histopathology', label: 'Histopathology Analysis' },
13
+ ];
14
+
15
+ return (
16
+ <aside className="w-64 bg-white border-r border-gray-200 p-4">
17
+ <div className="space-y-4">
18
+ {/* Gradient Header */}
19
+ <h3 className="text-lg font-semibold text-white mb-3 px-3 py-2 rounded-md bg-gradient-to-r from-blue-800 to-teal-600 shadow-sm">
20
+ New Test
21
+ </h3>
22
+
23
+ {/* Test Selection */}
24
+ <div className="flex flex-col gap-2">
25
+ {testTypes.map((test) => (
26
+ <label
27
+ key={test.value}
28
+ className={`flex items-center gap-3 px-3 py-2 rounded-lg cursor-pointer border transition-all duration-200
29
+ ${
30
+ selectedTest === test.value
31
+ ? 'border-blue-600 bg-blue-50 text-blue-900 font-medium shadow-sm'
32
+ : 'border-gray-200 hover:border-blue-300 hover:bg-gray-50 text-gray-700'
33
+ }`}
34
+ >
35
+ <input
36
+ type="radio"
37
+ name="testType"
38
+ value={test.value}
39
+ checked={selectedTest === test.value}
40
+ onChange={() => onTestChange(test.value)}
41
+ className="form-radio h-4 w-4 text-blue-600 focus:ring-blue-500"
42
+ />
43
+ <span>{test.label}</span>
44
+ </label>
45
+ ))}
46
  </div>
47
+
48
  {/* History */}
49
  <button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
50
  <FileTextIcon className="w-5 h-5" />
51
  <span>History</span>
52
  </button>
53
+
54
  {/* Help */}
55
  <button className="w-full flex items-center gap-3 px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors">
56
  <HelpCircleIcon className="w-5 h-5" />
57
  <span>Help</span>
58
  </button>
59
  </div>
60
+ </aside>
61
+ );
62
  }
frontend/src/components/UploadSection.tsx CHANGED
@@ -24,16 +24,16 @@ export function UploadSection({
24
 
25
  const modelOptions = {
26
  cytology: [
27
- { value: 'mwt', label: 'MWT' },
28
- { value: 'yolo', label: 'YOLOv8' },
29
  ],
30
  colposcopy: [
31
- { value: 'cin', label: 'Logistic-Colpo' },
32
-
33
  ],
34
  histopathology: [
35
- { value: 'histopathology', label: 'Path Foundation Model' },
36
-
37
  ],
38
  };
39
 
@@ -156,7 +156,11 @@ export function UploadSection({
156
  <button
157
  onClick={onAnalyze}
158
  disabled={!uploadedImage || !selectedModel}
159
- className="w-full mt-6 bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
 
 
 
 
160
  >
161
  Analyze
162
  </button>
@@ -174,16 +178,12 @@ export function UploadSection({
174
  (img, index) => (
175
  <div
176
  key={index}
177
- className={`w-20 h-20 rounded-lg border-2 cursor-pointer transition-transform hover:scale-105 hover:border-blue-500 overflow-hidden ${
178
- uploadedImage === img ? 'border-blue-600' : 'border-gray-300'
179
  }`}
180
  onClick={() => handleSampleClick(img)}
181
  >
182
- <img
183
- src={img}
184
- alt={`Sample ${index + 1}`}
185
- className="w-full h-full object-cover"
186
- />
187
  </div>
188
  )
189
  )}
 
24
 
25
  const modelOptions = {
26
  cytology: [
27
+ { value: 'mwt', label: 'Manalife_AI_MWT' },
28
+ { value: 'yolo', label: 'Manalife_AI_YOLOv8' },
29
  ],
30
  colposcopy: [
31
+ { value: 'cin', label: 'Manalife_MaANIA_Colpo' },
32
+
33
  ],
34
  histopathology: [
35
+ { value: 'histopathology', label: 'ManalifeAI__Path Foundation Model' },
36
+
37
  ],
38
  };
39
 
 
156
  <button
157
  onClick={onAnalyze}
158
  disabled={!uploadedImage || !selectedModel}
159
+ className={`w-full mt-6 text-white py-3 rounded-lg font-medium transition-colors ${
160
+ !uploadedImage || !selectedModel
161
+ ? 'bg-gray-300 disabled:cursor-not-allowed'
162
+ : 'bg-gradient-to-r from-blue-800 to-teal-600 hover:opacity-95'
163
+ }`}
164
  >
165
  Analyze
166
  </button>
 
178
  (img, index) => (
179
  <div
180
  key={index}
181
+ className={`w-20 h-20 rounded-lg border-2 cursor-pointer transition-transform hover:scale-105 overflow-hidden ${
182
+ uploadedImage === img ? 'ring-2 ring-offset-1 ring-blue-800' : 'border-gray-300'
183
  }`}
184
  onClick={() => handleSampleClick(img)}
185
  >
186
+ <img src={img} alt={`Sample ${index + 1}`} className="w-full h-full object-cover" />
 
 
 
 
187
  </div>
188
  )
189
  )}