Compare commits
2 Commits
1b78f1904d
...
7007478c3b
| Author | SHA1 | Date | |
|---|---|---|---|
| 7007478c3b | |||
| fb728d5033 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -175,3 +175,4 @@ cython_debug/
|
|||||||
.pypirc
|
.pypirc
|
||||||
|
|
||||||
/individual_images
|
/individual_images
|
||||||
|
*.xlsx
|
||||||
60
main.py
60
main.py
@@ -550,10 +550,10 @@ class UpdateOptodesWindow(QWidget):
|
|||||||
self.btn_browse_a = QPushButton("Browse .snirf")
|
self.btn_browse_a = QPushButton("Browse .snirf")
|
||||||
self.btn_browse_a.clicked.connect(self.browse_file_a)
|
self.btn_browse_a.clicked.connect(self.browse_file_a)
|
||||||
|
|
||||||
self.label_file_b = QLabel("TXT file:")
|
self.label_file_b = QLabel("Text file:")
|
||||||
self.line_edit_file_b = QLineEdit()
|
self.line_edit_file_b = QLineEdit()
|
||||||
self.line_edit_file_b.setReadOnly(True)
|
self.line_edit_file_b.setReadOnly(True)
|
||||||
self.btn_browse_b = QPushButton("Browse .txt")
|
self.btn_browse_b = QPushButton("Browse .txt/.xlsx")
|
||||||
self.btn_browse_b.clicked.connect(self.browse_file_b)
|
self.btn_browse_b.clicked.connect(self.browse_file_b)
|
||||||
|
|
||||||
self.label_suffix = QLabel("Suffix to append to filename:")
|
self.label_suffix = QLabel("Suffix to append to filename:")
|
||||||
@@ -574,9 +574,10 @@ class UpdateOptodesWindow(QWidget):
|
|||||||
|
|
||||||
self.description.setText("Some software when creating snirf files will insert a template of optode positions as the correct position of the optodes for the participant.<br>"
|
self.description.setText("Some software when creating snirf files will insert a template of optode positions as the correct position of the optodes for the participant.<br>"
|
||||||
"This is rarely correct as each head differs slightly in shape or size, and a lot of calculations require the optodes to be in the correct location.<br>"
|
"This is rarely correct as each head differs slightly in shape or size, and a lot of calculations require the optodes to be in the correct location.<br>"
|
||||||
"Using a .txt file, we can update the positions in the snirf file to match those of a digitization system such as one from Polhemus or elsewhere.<br>"
|
"Using a .txt or .xlsx file, we can update the positions in the snirf file to match those of a digitization system such as one from Polhemus or elsewhere.<br>"
|
||||||
"The .txt file should have the fiducials, detectors, and sources clearly labeled, followed by the x, y, and z coordinates seperated by a space.<br>"
|
"The .txt file should have the fiducials, detectors, and sources clearly labeled, followed by the x, y, and z coordinates seperated by a space.<br>"
|
||||||
"An example format of what a digitization text file should look like can be found <a href='custom_link'>by clicking here</a>.")
|
"An example format of what a digitization text file should look like can be found <a href='custom_link'>by clicking here</a>. Currently only .xlsx files directly exported from a<br>"
|
||||||
|
"Polhemus system are supported.")
|
||||||
|
|
||||||
self.description.linkActivated.connect(self.handle_link_click)
|
self.description.linkActivated.connect(self.handle_link_click)
|
||||||
layout.addWidget(self.description)
|
layout.addWidget(self.description)
|
||||||
@@ -605,8 +606,7 @@ class UpdateOptodesWindow(QWidget):
|
|||||||
file_a_layout.addWidget(file_a_container)
|
file_a_layout.addWidget(file_a_container)
|
||||||
layout.addLayout(file_a_layout)
|
layout.addLayout(file_a_layout)
|
||||||
|
|
||||||
|
help_text_b = "Provide a .txt file with labeled optodes (e.g., nz, rpa, lpa, d1, s1) and their x, y, z coordinates, or a .xlsx file from a Polhemius system."
|
||||||
help_text_b = "Provide a .txt file with labeled optodes (e.g., nz, rpa, lpa, d1, s1) and their x, y, z coordinates."
|
|
||||||
|
|
||||||
file_b_layout = QHBoxLayout()
|
file_b_layout = QHBoxLayout()
|
||||||
|
|
||||||
@@ -689,7 +689,7 @@ class UpdateOptodesWindow(QWidget):
|
|||||||
self.line_edit_file_a.setText(file_path)
|
self.line_edit_file_a.setText(file_path)
|
||||||
|
|
||||||
def browse_file_b(self):
|
def browse_file_b(self):
|
||||||
file_path, _ = QFileDialog.getOpenFileName(self, "Select TXT File", "", "Text Files (*.txt)")
|
file_path, _ = QFileDialog.getOpenFileName(self, "Select File", "", "Text Files (*.txt), Excel Files (*.xlsx)")
|
||||||
if file_path:
|
if file_path:
|
||||||
self.line_edit_file_b.setText(file_path)
|
self.line_edit_file_b.setText(file_path)
|
||||||
|
|
||||||
@@ -743,7 +743,10 @@ class UpdateOptodesWindow(QWidget):
|
|||||||
fiducials = {}
|
fiducials = {}
|
||||||
ch_positions = {}
|
ch_positions = {}
|
||||||
|
|
||||||
|
extension = Path(file_b).suffix
|
||||||
|
|
||||||
# Read the lines from the optode file
|
# Read the lines from the optode file
|
||||||
|
if extension == '.txt':
|
||||||
with open(file_b, 'r') as f:
|
with open(file_b, 'r') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.strip():
|
if line.strip():
|
||||||
@@ -759,6 +762,49 @@ class UpdateOptodesWindow(QWidget):
|
|||||||
else:
|
else:
|
||||||
ch_positions[ch_name.upper()] = coords
|
ch_positions[ch_name.upper()] = coords
|
||||||
|
|
||||||
|
elif extension == '.xlsx':
|
||||||
|
|
||||||
|
df = pd.read_excel(file_b, sheet_name='Sheet1')
|
||||||
|
|
||||||
|
def _get_block_data(df, block_id, row_mapping, scale=0.001):
|
||||||
|
"""Isolates a block, cleans numeric data, and returns a scaled dictionary."""
|
||||||
|
# 1. Isolate and clean
|
||||||
|
block = df[df['block_id'] == block_id].iloc[:, [1, 2, 3]].copy()
|
||||||
|
block = block.apply(pd.to_numeric, errors='coerce')
|
||||||
|
|
||||||
|
# 2. Extract into dictionary based on mapping
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
# If row_mapping is a dict (like {0: 'nz'}), use it directly
|
||||||
|
if isinstance(row_mapping, dict):
|
||||||
|
for row_idx, key in row_mapping.items():
|
||||||
|
if row_idx < len(block):
|
||||||
|
result[key] = block.iloc[row_idx].to_numpy(dtype=float) * scale
|
||||||
|
|
||||||
|
# If row_mapping is a string prefix (like 'D' or 'S'), auto-generate keys
|
||||||
|
elif isinstance(row_mapping, str):
|
||||||
|
for i in range(len(block)):
|
||||||
|
result[f"{row_mapping}{i+1}"] = block.iloc[i].to_numpy(dtype=float) * scale
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Identify blocks
|
||||||
|
is_empty = df.isnull().all(axis=1)
|
||||||
|
df['block_id'] = is_empty.cumsum()
|
||||||
|
clean_df = df[~is_empty].copy()
|
||||||
|
|
||||||
|
# Process Block 2: Landmarks
|
||||||
|
fiducials = _get_block_data(clean_df, 2, {0: 'nz', 2: 'rpa', 3: 'lpa'})
|
||||||
|
|
||||||
|
# Process Block 3: D-Points
|
||||||
|
d_points = _get_block_data(clean_df, 3, 'D')
|
||||||
|
|
||||||
|
# Process Block 4: S-Points
|
||||||
|
s_points = _get_block_data(clean_df, 4, 'S')
|
||||||
|
|
||||||
|
ch_positions = {**d_points, **s_points}
|
||||||
|
|
||||||
# Create montage with updated coords in head space
|
# Create montage with updated coords in head space
|
||||||
initial_montage = make_dig_montage(ch_pos=ch_positions, nasion=fiducials.get('nz'), lpa=fiducials.get('lpa'), rpa=fiducials.get('rpa'), coord_frame='head') # type: ignore
|
initial_montage = make_dig_montage(ch_pos=ch_positions, nasion=fiducials.get('nz'), lpa=fiducials.get('lpa'), rpa=fiducials.get('rpa'), coord_frame='head') # type: ignore
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user