Skip to content

Commit e7be5b7

Browse files
authored
Merge pull request #197 from CWRUbotix/dna
Dna
2 parents 09487f0 + aa7601a commit e7be5b7

File tree

4 files changed

+172
-0
lines changed

4 files changed

+172
-0
lines changed

src/surface/gui/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ Then `operator_app.py` and `pilot_app.py` inherit from App to make our two custo
99

1010
## Installation
1111

12+
Install pypdf with
13+
14+
```bash
15+
pip install pypdf --break-system-packages
16+
```
17+
1218
## Usage
1319

1420
Run the operator GUI with

src/surface/gui/gui/operator_app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from gui.widgets.ip_widget import IPWidget
99
from gui.widgets.logger import Logger
1010
from gui.widgets.tabs.carp_model_tab import CarpModelTab
11+
from gui.widgets.tabs.dna_tab import DNATab
1112
from gui.widgets.tabs.general_debug_tab import GeneralDebugTab
1213
from gui.widgets.tabs.photosphere_tab import PhotosphereTab
1314
from gui.widgets.tabs.shipwreck import ShipwreckTab
@@ -59,6 +60,7 @@ def __init__(self) -> None:
5960
self.tabs.addTab(PhotosphereTab(), 'Photosphere')
6061
self.tabs.addTab(CarpModelTab(), 'Carp Model')
6162
self.shipwreck_tab = ShipwreckTab()
63+
self.tabs.addTab(DNATab(), 'DNA Sample')
6264
self.tabs.addTab(self.shipwreck_tab, SHIPWRECK_TEXT)
6365
self.tabs.currentChanged.connect(self.changed_tabs)
6466
root_layout.addWidget(self.tabs)

src/surface/gui/gui/widgets/dna.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import re
2+
from pathlib import Path
3+
4+
from pypdf import PdfReader
5+
from PyQt6.QtWidgets import (
6+
QFileDialog,
7+
QHBoxLayout,
8+
QLabel,
9+
QLineEdit,
10+
QPlainTextEdit,
11+
QPushButton,
12+
QTextEdit,
13+
QVBoxLayout,
14+
QWidget,
15+
)
16+
17+
DNA_NEEDLES = [
18+
[
19+
'Bighead Carp',
20+
'AACTTAAATAAACAGATTATTCCACTAACAATTGATTCTCAAATTTATTACTGAATTATTAACTAAAATCTAACTCAAGTATATTATTAAAGTAAGAGACCACCTACTTATTTATATTAAGGTATTATATTCATGATAAGATCAAGGACAATAACAGTGGGGGTGGCGCAAAATGAACTATTACTTGCATCTGGTTTGGAATCTCACGGACATGGCTACAAAATTCCACCCCCGTTACATTATAACTGGCATATGGTTAAATGATGTGAGTACATACTCCTCATTAACCCCACATGCCGAGCATTCTTTTAT',
21+
],
22+
[
23+
'Silver Carp',
24+
'CCTGAGAAAAGAGTTGTTCCACTATAATTGGTTCTCAAATATTTCCTTGAAATATTAACTTCTATTTAATTTAACTATATTAATGTAGTAAGAAACCACCTACTGGTTTATATTAAGGTATTCTATTCATGATAAGATCAGGGACAATAATCGTGGGGGTGGCGCAGAATGAACTATTACTTGCATTTGGC',
25+
],
26+
[
27+
'Grass Carp',
28+
'GAGTTTCTGACTTCTACCCCCTTCTTTCCTCCTACTATTAGCCTCTTCTGGTGTTGAGGCCGGAGCTGGAACAGGGTGAACAG',
29+
],
30+
[
31+
'Black Carp',
32+
'ACACCACGTTCTTTGACCCAGCAGGCGGAGGAGACCCAATCCTATATCAACACCTGTTCTGATTCTTCGGCCACCCAGAAGTTTACATTCTTATTTTACCCGGGTTTGGGATCATTTCAC',
33+
],
34+
]
35+
36+
37+
class DNA(QWidget):
38+
def __init__(self) -> None:
39+
super().__init__()
40+
41+
self.root_layout = QVBoxLayout()
42+
self.base_layout = QHBoxLayout()
43+
44+
self.input_layout = QVBoxLayout()
45+
self.auto_output_layout = QVBoxLayout()
46+
self.manual_output_layout = QVBoxLayout()
47+
48+
file_picker_layout = QHBoxLayout()
49+
50+
self.sample = QPlainTextEdit()
51+
52+
file_browse = QPushButton('Browse')
53+
file_browse.clicked.connect(self.open_file_dialog)
54+
self.filename = QLineEdit()
55+
56+
show_button = QPushButton('Search Database', None)
57+
58+
show_button.clicked.connect(self.display_result)
59+
60+
self.check_text = QTextEdit()
61+
62+
self.input_layout.addWidget(QLabel('Select PDF File: '))
63+
file_picker_layout.addWidget(self.filename)
64+
file_picker_layout.addWidget(file_browse)
65+
self.input_layout.addLayout(file_picker_layout)
66+
67+
self.input_layout.addWidget(QLabel('Or enter sample directly: '))
68+
self.input_layout.addWidget(self.sample)
69+
70+
self.input_layout.addWidget(show_button)
71+
72+
self.base_layout.addLayout(self.input_layout)
73+
74+
self.base_layout.addLayout(self.auto_output_layout)
75+
self.base_layout.addLayout(self.manual_output_layout)
76+
self.root_layout.addLayout(self.base_layout)
77+
self.root_layout.addWidget(QLabel('Check DNA Samples:'))
78+
self.root_layout.addWidget(self.check_text)
79+
80+
self.manual_result: QLabel | None = None
81+
self.auto_results: list[QLabel] = []
82+
83+
self.setLayout(self.root_layout)
84+
85+
def open_file_dialog(self) -> None:
86+
filename, _ = QFileDialog.getOpenFileName(
87+
caption='Select a File', filter='PDF files (*.pdf)'
88+
)
89+
if filename:
90+
path = Path(filename)
91+
self.text_file = str(path)
92+
self.filename.setText(self.text_file)
93+
94+
def find_samples(self, pdf_file: str) -> list[str]:
95+
reader = PdfReader(pdf_file)
96+
samples = []
97+
self.check_text.setText('')
98+
for page in reader.pages:
99+
text = page.extract_text()
100+
last_index = 0
101+
while text.find('Unknown Sample', last_index) != -1:
102+
current_index = text.find(
103+
'Unknown Sample', last_index
104+
) # Roughly identify location of next sample
105+
sample_index = text.find('\n', current_index) + 1 # Find start of sample
106+
sample_index_end = text.find(' \n', sample_index) # Find end of sample
107+
sample = text[sample_index:sample_index_end].replace('\n', '')
108+
samples.append(sample)
109+
self.check_sample(sample, len(samples))
110+
last_index = sample_index_end
111+
return samples
112+
113+
def check_sample(self, sample: str, sample_num: int) -> None:
114+
# Check if sample has characters not corresponding to a DNA base
115+
if sample.replace('A', '').replace('C', '').replace('G', '').replace('T', '') != '':
116+
self.check_text.setText(
117+
'Sample '
118+
+ str(sample_num)
119+
+ ' has characters other than ACGT\n\n'
120+
+ self.check_text.toPlainText()
121+
)
122+
123+
# Display all samples for manual checking
124+
self.check_text.setText(
125+
self.check_text.toPlainText() + ('Sample ' + str(sample_num) + ': ' + sample) + '\n\n'
126+
)
127+
128+
def display_result(self) -> None:
129+
for result in self.auto_results:
130+
self.auto_output_layout.removeWidget(result)
131+
self.manual_output_layout.removeWidget(self.manual_result)
132+
133+
if self.filename.text():
134+
self.auto_results = []
135+
samples = self.find_samples(self.filename.text())
136+
for i, sample in enumerate(samples):
137+
self.auto_results.append(QLabel(str(i + 1) + ': ' + self.search(sample)))
138+
if self.sample.toPlainText():
139+
sample = self.sample.toPlainText()
140+
sample = re.sub('[^ACGT]', '', sample) # Remove every character except ACGT
141+
self.manual_result = QLabel('Manual: ' + self.search(sample))
142+
143+
for result in self.auto_results:
144+
self.auto_output_layout.addWidget(result)
145+
self.manual_output_layout.addWidget(self.manual_result)
146+
147+
def search(self, sample: str) -> str:
148+
for name, substr in DNA_NEEDLES:
149+
if sample.find(substr) != -1:
150+
return name
151+
152+
return 'No match'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from PyQt6.QtWidgets import QVBoxLayout, QWidget
2+
3+
from gui.widgets.dna import DNA
4+
5+
6+
class DNATab(QWidget):
7+
def __init__(self) -> None:
8+
super().__init__()
9+
10+
root_layout = QVBoxLayout()
11+
root_layout.addWidget(DNA())
12+
self.setLayout(root_layout)

0 commit comments

Comments
 (0)