Compare commits
2 Commits
1b78f1904d
...
7007478c3b
| Author | SHA1 | Date | |
|---|---|---|---|
| 7007478c3b | |||
| fb728d5033 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -175,3 +175,4 @@ cython_debug/
|
||||
.pypirc
|
||||
|
||||
/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.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.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.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>"
|
||||
"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>"
|
||||
"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)
|
||||
layout.addWidget(self.description)
|
||||
@@ -605,8 +606,7 @@ class UpdateOptodesWindow(QWidget):
|
||||
file_a_layout.addWidget(file_a_container)
|
||||
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."
|
||||
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."
|
||||
|
||||
file_b_layout = QHBoxLayout()
|
||||
|
||||
@@ -689,7 +689,7 @@ class UpdateOptodesWindow(QWidget):
|
||||
self.line_edit_file_a.setText(file_path)
|
||||
|
||||
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:
|
||||
self.line_edit_file_b.setText(file_path)
|
||||
|
||||
@@ -743,7 +743,10 @@ class UpdateOptodesWindow(QWidget):
|
||||
fiducials = {}
|
||||
ch_positions = {}
|
||||
|
||||
extension = Path(file_b).suffix
|
||||
|
||||
# Read the lines from the optode file
|
||||
if extension == '.txt':
|
||||
with open(file_b, 'r') as f:
|
||||
for line in f:
|
||||
if line.strip():
|
||||
@@ -759,6 +762,49 @@ class UpdateOptodesWindow(QWidget):
|
||||
else:
|
||||
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
|
||||
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