Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 61 additions & 19 deletions src/pypulseq/Sequence/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def set_block(self, block_index: int, *args: SimpleNamespace) -> None:
if hasattr(event, 'id'):
adc_id = event.id
else:
adc_id = register_adc_event(self, event)
adc_id, _ = register_adc_event(self, event)

new_block[5] = adc_id
duration = max(duration, event.delay + event.num_samples * event.dwell + event.dead_time)
Expand Down Expand Up @@ -318,6 +318,8 @@ def get_block(self, block_index: int) -> SimpleNamespace:
else:
block.rf = self.rf_from_lib_data(self.rf_library.data[event_ind[1]], 'u') # Undefined type/use

# TODO: add optional rf ID from raw_block

# Gradients
grad_channels = ['gx', 'gy', 'gz']
for i in range(len(grad_channels)):
Expand Down Expand Up @@ -371,18 +373,31 @@ def get_block(self, block_index: int) -> SimpleNamespace:
# ADC
if event_ind[5] > 0:
lib_data = self.adc_library.data[event_ind[5]]
shape_id_phase_modulation = lib_data[-2]
if shape_id_phase_modulation:
shape_data = self.shape_library.data[shape_id_phase_modulation]
compressed = SimpleNamespace()
compressed.num_samples = shape_data[0]
compressed.data = shape_data[1:]
phase_shape = decompress_shape(compressed)
else:
phase_shape = np.array([], dtype=float)

adc = SimpleNamespace()
(
adc.num_samples,
adc.dwell,
adc.delay,
adc.freq_offset,
adc.phase_offset,
adc.dead_time,
) = [lib_data[x] for x in range(6)]
adc.num_samples = lib_data[0]
adc.dwell = lib_data[1]
adc.delay = lib_data[2]
adc.freq_ppm = lib_data[3]
adc.phase_ppm = lib_data[4]
adc.freq_offset = lib_data[5]
adc.phase_offset = lib_data[6]
adc.phase_modulation = phase_shape
adc.dead_time = self.system.adc_dead_time
adc.num_samples = int(adc.num_samples)
adc.type = 'adc'

# TODO: add optional adc ID from raw_block

block.adc = adc

# Triggers
Expand Down Expand Up @@ -449,7 +464,7 @@ def get_block(self, block_index: int) -> SimpleNamespace:
return block


def register_adc_event(self, event: EventLibrary) -> int:
def register_adc_event(self, event: EventLibrary) -> Tuple[int, List[int]]:
"""

Parameters
Expand All @@ -459,25 +474,52 @@ def register_adc_event(self, event: EventLibrary) -> int:

Returns
-------
int
ID of registered ADC event.
int, [int, ...]
ID of registered RF event, list of shape IDs
"""
surely_new = False

# Handle phase modulation
if not hasattr(event, 'phase_modulation') or event.phase_modulation is None or len(event.phase_modulation) == 0:
shape_id = 0
else:
if hasattr(event, 'shape_id'):
shape_id = event.shape_id
else:
phase_shape = compress_shape(np.asarray(event.phase_modulation).flatten())
shape_data = np.concatenate(([phase_shape.num_samples], phase_shape.data))
shape_id, shape_found = self.shape_library.find_or_insert(shape_data)
if not shape_found:
surely_new = True

# Construct the ADC event data
data = (
event.num_samples,
event.dwell,
event.delay,
max(event.delay, event.dead_time),
event.freq_ppm,
event.phase_ppm,
event.freq_offset,
event.phase_offset,
shape_id,
event.dead_time,
)
adc_id, found = self.adc_library.find_or_insert(new_data=data)

# Clear block cache because ADC was overwritten
# TODO: Could find only the blocks that are affected by the changes
if self.use_block_cache and found:
self.block_cache.clear()
# Insert or find/insert into libraryAdd commentMore actions
if surely_new:
adc_id = self.adc_library.insert(0, data)
else:
adc_id, found = self.adc_library.find_or_insert(data)

# Clear block cache if overwritten
if self.use_block_cache and found:
self.block_cache.clear()

# Optional mapping
if hasattr(event, 'name'):
self.adc_id_to_name_map[adc_id] = event.name

return adc_id
return adc_id, shape_id


def register_control_event(self, event: SimpleNamespace) -> int:
Expand Down
14 changes: 11 additions & 3 deletions src/pypulseq/Sequence/read_seq.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,17 @@ def read(self, path: str, detect_rf_use: bool = False, remove_duplicates: bool =
else:
self.grad_library = __read_events(input_file, (1, 1e-6, 1e-6, 1e-6, 1e-6), 't', self.grad_library)
elif section == '[ADC]':
self.adc_library = __read_events(
input_file, (1, 1e-9, 1e-6, 1, 1), event_library=self.adc_library, append=self.system.adc_dead_time
)
if version_combined >= 1005000: # 1.5.x format
self.adc_library = __read_events(
input_file,
(1, 1e-9, 1e-6, 1, 1, 1, 1, 1),
event_library=self.adc_library,
append=self.system.adc_dead_time,
)
else: # 1.4.x format and below
self.adc_library = __read_events(
input_file, (1, 1e-9, 1e-6, 1, 1), event_library=self.adc_library, append=self.system.adc_dead_time
)
elif section == '[DELAYS]':
if version_combined >= 1004000:
raise RuntimeError('Pulseq file revision 1.4.0 and above MUST NOT contain [DELAYS] section')
Expand Down
3 changes: 2 additions & 1 deletion src/pypulseq/Sequence/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def __init__(self, system: Union[Opts, None] = None, use_block_cache: bool = Tru
self.signature_file = ''
self.signature_value = ''
self.rf_id_to_name_map = {}
self.adc_id_to_name_map = {}

self.block_durations = {}
self.extension_numeric_idx = []
Expand Down Expand Up @@ -1224,7 +1225,7 @@ def remove_duplicates(self, in_place: bool = False) -> Self:
seq_copy.block_events[block_id][1] = mapping[seq_copy.block_events[block_id][1]]

# Filter duplicates in ADC library
seq_copy.adc_library, mapping = seq_copy.adc_library.remove_duplicates((0, -9, -6, 6, 6, 6))
seq_copy.adc_library, mapping = seq_copy.adc_library.remove_duplicates((0, -9, -6, 6, 6, 6, 6, 6, 6))

# Remap ADC event IDs
for block_id in seq_copy.block_events:
Expand Down
8 changes: 4 additions & 4 deletions src/pypulseq/Sequence/write_seq.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ def write(self, file_name: Union[str, Path], create_signature, remove_duplicates

if len(self.adc_library.data) != 0:
output_file.write('# Format of ADC events:\n')
output_file.write('# id num dwell delay freq phase\n')
output_file.write('# .. .. ns us Hz rad\n')
output_file.write('# id num dwell delay freqPPM phasePPM freq phase phase_id\n')
output_file.write('# .. .. ns us ppm rad/MHz Hz rad ..\n')
output_file.write('[ADC]\n')
id_format_str = '{:.0f} {:.0f} {:.0f} {:.0f} {:g} {:g}\n' # Refer lines 20-21
id_format_str = '{:.0f} {:.0f} {:.0f} {:.0f} {:g} {:g} {:g} {:g} {:.0f}\n' # Refer lines 20-21
for k in self.adc_library.data:
data = np.multiply(self.adc_library.data[k][0:5], [1, 1e9, 1e6, 1, 1])
data = np.multiply(self.adc_library.data[k][0:8], [1, 1e9, 1e6, 1, 1, 1, 1, 1])
s = id_format_str.format(k, *data)
output_file.write(s)
output_file.write('\n')
Expand Down
18 changes: 18 additions & 0 deletions src/pypulseq/make_adc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from typing import List, Optional, Tuple, Union
from warnings import warn

import numpy as np

from pypulseq.opts import Opts
from pypulseq.utils.tracing import trace, trace_enabled

Expand All @@ -16,6 +18,9 @@ def make_adc(
freq_offset: float = 0,
phase_offset: float = 0,
system: Union[Opts, None] = None,
freq_ppm: float = 0,
phase_ppm: float = 0,
phase_modulation: np.ndarray = None,
) -> SimpleNamespace:
"""
Create an ADC readout event.
Expand All @@ -36,6 +41,13 @@ def make_adc(
Frequency offset of ADC readout event.
phase_offset : float, default=0
Phase offset of ADC readout event.
freq_ppm : float, default=0
PPM frequency offset of ADC readout event.
phase_ppm : float, default=0
PPM phase offset of ADC readout event.
phase_modulation : numpy.ndarray, default=None
Phase modulation array for FOV shifting.
If provided, it must have `num_samples` number of samples.

Returns
-------
Expand All @@ -57,11 +69,17 @@ def make_adc(
adc.delay = delay
adc.freq_offset = freq_offset
adc.phase_offset = phase_offset
adc.freq_ppm = freq_ppm
adc.phase_ppm = phase_ppm
adc.dead_time = system.adc_dead_time

if (dwell == 0 and duration == 0) or (dwell > 0 and duration > 0):
raise ValueError('Either dwell or duration must be defined')

if phase_modulation is not None and len(phase_modulation) != num_samples:
raise ValueError('ADC Phase modulation vector must have the same length as the number of samples')
adc.phase_modulation = phase_modulation

if duration > 0:
adc.dwell = duration / num_samples

Expand Down
8 changes: 4 additions & 4 deletions tests/expected_output/seq2.seq
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ TotalDuration 0.0142
4 102459 240 9520 240 0

# Format of ADC events:
# id num dwell delay freq phase
# .. .. ns us Hz rad
# id num dwell delay freqPPM phasePPM freq phase phase_id
# .. .. ns us ppm rad/MHz Hz rad ..
[ADC]
1 100 100000 0 0 0
1 100 100000 0 0 0 0 0 0

# Sequence Shapes
[SHAPES]
Expand All @@ -70,4 +70,4 @@ num_samples 2
# It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE]
# The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be stripped away for recalculating/verification)
Type md5
Hash 7e8bfc27296bebb59901d7f6c2595b75
Hash 84be8c860fdbebf68977a6779ae84cb4
8 changes: 4 additions & 4 deletions tests/expected_output/seq3.seq
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ TotalDuration 0.12722
12 1.66667e+06 240 0 240 0

# Format of ADC events:
# id num dwell delay freq phase
# .. .. ns us Hz rad
# id num dwell delay freqPPM phasePPM freq phase phase_id
# .. .. ns us ppm rad/MHz Hz rad ..
[ADC]
1 100 100000 0 0 0
1 100 100000 0 0 0 0 0 0

# Format of extension lists:
# id type ref next_id
Expand Down Expand Up @@ -133,4 +133,4 @@ num_samples 2
# It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE]
# The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be stripped away for recalculating/verification)
Type md5
Hash 4ad305ffb67000bc974f15d48411ce1c
Hash 890a2a51ec2754a0007f5e1bda944975
8 changes: 4 additions & 4 deletions tests/expected_output/seq4.seq
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ TotalDuration 0.12722
12 1.66667e+06 240 0 240 0

# Format of ADC events:
# id num dwell delay freq phase
# .. .. ns us Hz rad
# id num dwell delay freqPPM phasePPM freq phase phase_id
# .. .. ns us ppm rad/MHz Hz rad ..
[ADC]
1 100 100000 0 0 0
1 100 100000 0 0 0 0 0 0

# Format of extension lists:
# id type ref next_id
Expand Down Expand Up @@ -151,4 +151,4 @@ num_samples 2
# It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE]
# The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be stripped away for recalculating/verification)
Type md5
Hash 5a1ef322af8969e98bb7ae996d630839
Hash 74a5b1a80817326ed54758f9cbe8c446
8 changes: 4 additions & 4 deletions tests/expected_output/write_epi.seq
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,10 @@ TotalDuration 0.15405
7 -1.13636e+06 210 260 210 0

# Format of ADC events:
# id num dwell delay freq phase
# .. .. ns us Hz rad
# id num dwell delay freqPPM phasePPM freq phase phase_id
# .. .. ns us ppm rad/MHz Hz rad ..
[ADC]
1 64 4000 214 0 0
1 64 4000 214 0 0 0 0 0

# Sequence Shapes
[SHAPES]
Expand Down Expand Up @@ -3461,4 +3461,4 @@ num_samples 3000
# It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE]
# The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be stripped away for recalculating/verification)
Type md5
Hash 28216862a9ddf0aee4d31a5fbecc4bb6
Hash e6dba7be0f6647ea5e8ceb85c5afe0ac
8 changes: 4 additions & 4 deletions tests/expected_output/write_epi_label.seq
Original file line number Diff line number Diff line change
Expand Up @@ -5680,10 +5680,10 @@ TotalDuration 4.32868
8 1.35307e+06 250 610 250 0

# Format of ADC events:
# id num dwell delay freq phase
# .. .. ns us Hz rad
# id num dwell delay freqPPM phasePPM freq phase phase_id
# .. .. ns us ppm rad/MHz Hz rad ..
[ADC]
1 64 4000 214 0 0
1 64 4000 214 0 0 0 0 0

# Format of extension lists:
# id type ref next_id
Expand Down Expand Up @@ -8763,4 +8763,4 @@ num_samples 3000
# It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE]
# The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be stripped away for recalculating/verification)
Type md5
Hash 7094537c10608235728fd41a2c43ec07
Hash b2950251a04185feafc64b2bb9101dca
8 changes: 4 additions & 4 deletions tests/expected_output/write_epi_se.seq
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ TotalDuration 0.08315
8 -781250 150 320 150 0

# Format of ADC events:
# id num dwell delay freq phase
# .. .. ns us Hz rad
# id num dwell delay freqPPM phasePPM freq phase phase_id
# .. .. ns us ppm rad/MHz Hz rad ..
[ADC]
1 64 5000 150 0 0
1 64 5000 150 0 0 0 0 0

# Sequence Shapes
[SHAPES]
Expand Down Expand Up @@ -3222,4 +3222,4 @@ num_samples 2
# It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE]
# The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be stripped away for recalculating/verification)
Type md5
Hash cc67cded0ce3d84780a1d526cd81d25c
Hash fa0b012993045142ae20c0ec821d5ad7
Loading
Loading