Rathapoom commited on
Commit
e1c1c45
·
verified ·
1 Parent(s): 6b4b766

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -60
app.py CHANGED
@@ -1,98 +1,112 @@
1
  import gradio as gr
 
2
  from transformers import pipeline
3
  import re
 
4
 
5
- # 1. โหลดโมเดล NER จาก Hugging Face
6
- # device=-1 หมายถึงให้ใช้ CPU ซึ่งเหมาะกับ Free tier ของ HF Spaces
7
  print("กำลังโหลดโมเดล...")
8
- ner_pipeline = pipeline("token-classification", model="loolootech/no-name-ner-th", device=-1)
 
 
 
 
 
 
 
9
  print("โมเดลพร้อมใช้งานแล้ว")
10
 
11
- # 2. ฟังก์ชันสำหรับรวม Token ที่อยู่ติดกัน (B-PERSON, I-PERSON -> PERSON)
 
12
  def merge_entities(ner_results):
13
  merged_entities = []
14
  current_entity = None
15
-
16
  for entity in ner_results:
17
- # ลบ B- หรือ I- prefix ออกไปเพื่อให้ได้ประเภท entity ที่แท้จริง
18
  entity_type = re.sub(r'^[BI]-', '', entity['entity'])
19
-
20
  if current_entity and entity['start'] == current_entity['end'] and entity_type == current_entity['type']:
21
- # ถ้า token นี้อยู่ติดกับ entity ก่อนหน้าและเป็นประเภทเดียวกัน ให้รวมกัน
22
  current_entity['word'] += entity['word']
23
  current_entity['end'] = entity['end']
24
  current_entity['score'] = max(current_entity['score'], entity['score'])
25
  else:
26
- # ถ้าไม่ใช่ ให้เริ่มนับเป็น entity ใหม่
27
  if current_entity:
28
  merged_entities.append(current_entity)
29
-
30
  current_entity = {
31
- 'type': entity_type,
32
- 'word': entity['word'],
33
- 'start': entity['start'],
34
- 'end': entity['end'],
35
- 'score': entity['score']
36
  }
37
-
38
- # เพิ่ม entity สุดท้ายที่ค้างไว้
39
  if current_entity:
40
  merged_entities.append(current_entity)
41
-
42
  return merged_entities
43
 
44
- # 3. ฟังก์ชันหลักสำหรับ De-identification
45
- def deidentify_text(text):
46
- if not text.strip():
47
- return "กรุณาใส่ข้อความ", ""
48
 
49
- # รัน NER pipeline
50
- ner_results = ner_pipeline(text)
 
 
51
 
52
- # รวม token ที่อยู่ติดกัน
53
  merged = merge_entities(ner_results)
54
-
55
- # สร้างข้อความที่ไฮไลท์ entity ต่างๆ เพื่อให้เห็นภาพ
56
- highlighted_text = ""
57
- last_index = 0
58
- for entity in merged:
59
- start, end, label, word = entity['start'], entity['end'], entity['type'], entity['word']
60
- # เพิ่มส่วนของข้อความที่ไม่ได้ถูกระบุว่าเป็น entity
61
- highlighted_text += text[last_index:start]
62
- # เพิ่มส่วนของ entity ที่ไฮไลท์
63
- highlighted_text += f" <mark>{word}**[{label}]**</mark> "
64
- last_index = end
65
- # เพิ่มข้อความส่วนที่เหลือ
66
- highlighted_text += text[last_index:]
67
-
68
- # ทำการแทนที่ (Redaction) จากหลังมาหน้าเพื่อไม่ให้ index เพี้ยน
69
  redacted_text = text
70
  for entity in reversed(merged):
71
  start, end, label = entity['start'], entity['end'], entity['type']
72
  redacted_text = redacted_text[:start] + f"[{label}]" + redacted_text[end:]
73
-
74
- return redacted_text, highlighted_text
75
-
76
-
77
- # 4. สร้างหน้าเว็บด้วย Gradio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  iface = gr.Interface(
79
- fn=deidentify_text,
80
- inputs=gr.Textbox(
81
- lines=5,
82
- label="ข้อความที่ต้องการตรวจสอบ (Input Text)",
83
- placeholder="เช่น: คุณสมชายเป็นอะไรมาครับวันนี้ อ๋อวันนี้ปวดตับครับ งั้นวันนี้หมอขอตรวจละเอียดหน่อยนะ ได้เลยครับน้องมาร์ค"
84
- ),
85
- outputs=[
86
- gr.Textbox(label="ข้อความที่ปกปิดข้อมูลแล้ว (Redacted Text)"),
87
- gr.Markdown(label="ผลลัพธ์พร้อมไฮไลท์ (Highlighted Entities)")
88
  ],
89
- title="👩‍⚕️ Thai Medical NER De-identification Demo",
90
- description="ทดสอบโมเดล `loolootech/no-name-ner-th` สำหรับการตรวจจับและปกปิดข้อมูลส่วนบุคคลในข้อความภาษาไทยทางการแพทย์\n\n**Entity ที่รองรับ:** PERSON, PHONE, EMAIL, ADDRESS, DATE, NATIONAL_ID, HOSPITAL_IDS",
91
- examples=[
92
- ["คุณสมชายเป็นอะไรมาครับวันนี้ อ๋อวันนี้ปวดตับครับ งั้นวันนี้หมอขอตรวจละเอียดหน่อยนะ ได้เลยครับน้องมาร์ค"],
93
- ["คนไข้ชื่อสมศรี มากี่โมง เบอร์โทร 081-234-5678 นัดตรวจวันที่ 15/10/2568"],
94
- ["ส่งผลตรวจไปที่ [email protected] ด้วยครับ เลขบัตรประชาชนคือ 1234567890123"]
95
  ],
 
 
96
  allow_flagging="never"
97
  )
98
 
 
1
  import gradio as gr
2
+ import pandas as pd
3
  from transformers import pipeline
4
  import re
5
+ import os
6
 
7
+ # 1. โหลดโมเดล NER (เหมือนเดิม)
 
8
  print("กำลังโหลดโมเดล...")
9
+ # ตรวจสอบว่ามี HF_TOKEN ใน Secrets หรือไม่
10
+ hf_token = os.getenv("HF_TOKEN")
11
+ ner_pipeline = pipeline(
12
+ "token-classification",
13
+ model="loolootech/no-name-ner-th",
14
+ device=-1,
15
+ token=hf_token # ส่ง Token ไปด้วยตอนโหลดโมเดล
16
+ )
17
  print("โมเดลพร้อมใช้งานแล้ว")
18
 
19
+
20
+ # 2. ฟังก์ชันสำหรับรวม Token (เหมือนเดิม)
21
  def merge_entities(ner_results):
22
  merged_entities = []
23
  current_entity = None
 
24
  for entity in ner_results:
 
25
  entity_type = re.sub(r'^[BI]-', '', entity['entity'])
 
26
  if current_entity and entity['start'] == current_entity['end'] and entity_type == current_entity['type']:
 
27
  current_entity['word'] += entity['word']
28
  current_entity['end'] = entity['end']
29
  current_entity['score'] = max(current_entity['score'], entity['score'])
30
  else:
 
31
  if current_entity:
32
  merged_entities.append(current_entity)
 
33
  current_entity = {
34
+ 'type': entity_type, 'word': entity['word'],
35
+ 'start': entity['start'], 'end': entity['end'], 'score': entity['score']
 
 
 
36
  }
 
 
37
  if current_entity:
38
  merged_entities.append(current_entity)
 
39
  return merged_entities
40
 
 
 
 
 
41
 
42
+ # 3. ฟังก์ชันหลักสำหรับ De-identification ของข้อความ 1 บรรทัด (เหมือนเดิม)
43
+ def deidentify_single_text(text):
44
+ if pd.isna(text) or not isinstance(text, str) or not text.strip():
45
+ return "" # คืนค่าเป็นสตริงว่างถ้าข้อมูลเป็นค่าว่าง, ไม่ใช่ข้อความ, หรือเป็นช่องว่าง
46
 
47
+ ner_results = ner_pipeline(text)
48
  merged = merge_entities(ner_results)
49
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  redacted_text = text
51
  for entity in reversed(merged):
52
  start, end, label = entity['start'], entity['end'], entity['type']
53
  redacted_text = redacted_text[:start] + f"[{label}]" + redacted_text[end:]
54
+
55
+ return redacted_text
56
+
57
+
58
+ # 4. [ใหม่] ฟังก์ชันสำห���ับประมวลผลไฟล์ที่อัปโหลด
59
+ def process_file(uploaded_file, column_name, progress=gr.Progress(track_tqdm=True)):
60
+ if uploaded_file is None:
61
+ raise gr.Error("กรุณาอัปโหลดไฟล์ก่อน")
62
+ if not column_name:
63
+ raise gr.Error("กรุณาระบุ 'ชื่อคอลัมน์' ที่ต้องการตรวจสอบ")
64
+
65
+ file_path = uploaded_file.name
66
+
67
+ # อ่านไฟล์ด้วย Pandas
68
+ try:
69
+ if file_path.endswith('.csv'):
70
+ df = pd.read_csv(file_path)
71
+ elif file_path.endswith(('.xlsx', '.xls')):
72
+ df = pd.read_excel(file_path)
73
+ else:
74
+ raise gr.Error("ไฟล์ไม่รองรับ กรุณาอัปโหลด .csv หรือ .xlsx เท่านั้น")
75
+ except Exception as e:
76
+ raise gr.Error(f"ไม่สามารถอ่านไฟล์ได้: {e}")
77
+
78
+ # ตรวจสอบว่าชื่อคอลัมน์มีอยู่จริงในไฟล์หรือไม่
79
+ if column_name not in df.columns:
80
+ raise gr.Error(f"ไม่พบคอลัมน์ '{column_name}' ในไฟล์ของคุณ คอลัมน์ที่มีคือ: {list(df.columns)}")
81
+
82
+ # สร้างชื่อคอลัมน์ใหม่สำหรับผลลัพธ์
83
+ output_column_name = f"redacted_{column_name}"
84
+
85
+ # ประมวลผลข้อมูลในคอลัมน์ที่เลือก และแสดง progress bar
86
+ # ใช้ .astype(str) เพื่อแปลงข้อมูลทุกอย่างเป็นข้อความก่อนประมวลผล ป้องกัน error
87
+ df[output_column_name] = df[column_name].astype(str).progress_apply(deidentify_single_text)
88
+
89
+ # สร้างไฟล์ผลลัพธ์เพื่อให้ผู้ใช้ดาวน์โหลด
90
+ output_filepath = "processed_output.csv"
91
+ # ใช้ encoding 'utf-8-sig' เพื่อให้เปิดใน Excel ภาษาไทยไม่เพี้ยน
92
+ df.to_csv(output_filepath, index=False, encoding='utf-8-sig')
93
+
94
+ return df, output_filepath
95
+
96
+
97
+ # 5. [ใหม่] สร้างหน้าเว็บ Gradio สำหรับอัปโหลดไฟล์
98
  iface = gr.Interface(
99
+ fn=process_file,
100
+ inputs=[
101
+ gr.File(label="อัปโหลดไฟล์ CSV หรือ Excel", file_types=[".csv", ".xlsx", ".xls"]),
102
+ gr.Textbox(label="ชื่อคอลัมน์ที่ต้องการตรวจสอบ (Column Name)", placeholder="เช่น: note, detail, description")
 
 
 
 
 
103
  ],
104
+ outputs=[
105
+ gr.DataFrame(label="ตารางผลลัพธ์ (Output Table Preview)", wrap=True),
106
+ gr.File(label="ดาวน์โหลดผลลัพธ์ (Download Result as CSV)")
 
 
 
107
  ],
108
+ title="📁 Bulk De-identification for CSV/Excel",
109
+ description="อัปโหลดไฟล์ตาราง (CSV, Excel) ระบุชื่อคอลัมน์ที่มีข้อความที่ต้องการปกปิดข้อมูลส่วนบุคคล แล้วระบบจะประมวลผลและสร้างไฟล์ใหม่ให้ดาวน์โหลด",
110
  allow_flagging="never"
111
  )
112