Skip to content

Fix scanner signature error and add binary signature verification#402

Open
albertx-wang wants to merge 4 commits into
imr-framework:masterfrom
albertx-wang:fix-binary-signature
Open

Fix scanner signature error and add binary signature verification#402
albertx-wang wants to merge 4 commits into
imr-framework:masterfrom
albertx-wang:fix-binary-signature

Conversation

@albertx-wang

Copy link
Copy Markdown
  • read_binary.py and write_binary.py:
    Added binary .bseq read/write support with MD5 signature serialization and deserialization.

  • read_seq.py and write_seq.py:
    Updated text .seq signature handling. write_seq.py now computes the MD5 hash from raw file bytes before appending [SIGNATURE], and read_seq.py restores the signature metadata consistently when reading signed text files.

  • verify_file_signature.py:
    Added a helper that recomputes the MD5 hash over the signed portion of text or binary Pulseq files and compares it with the stored signature, matching the MD5 verification behavior of MATLAB verifyFileSignature.m.

  • sequence.py:
    Exposed read_binary() and write_binary() on Sequence, and added shared binary section codes used by binary IO and signature verification.

  • test_binary_signature.py:
    Added binary signature roundtrip coverage for write_binary(), read_binary(), and verify_file_signature(), matching the binary signature roundtrip covered by MATLAB testBinarySignature.m.

albertx-wang and others added 2 commits June 10, 2026 09:48
…port and verification

* read_binary.py and write_binary.py:
Added binary .bseq read/write support with MD5 signature serialization and deserialization.

* read_seq.py and write_seq.py:
Updated text .seq signature handling. write_seq.py now computes the MD5 hash from raw file bytes before appending [SIGNATURE], and read_seq.py restores the signature metadata consistently when reading signed text files.

* verify_file_signature.py:
Added a helper that recomputes the MD5 hash over the signed portion of text or binary Pulseq files and compares it with the stored signature, matching the MD5 verification behavior of MATLAB verifyFileSignature.m.

* sequence.py:
Exposed read_binary() and write_binary() on Sequence, and added shared binary section codes used by binary IO and signature verification.

* test_binary_signature.py:
Added binary signature roundtrip coverage for write_binary(), read_binary(), and verify_file_signature(), matching the binary signature roundtrip covered by MATLAB testBinarySignature.m.
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
/home/runner/.local/lib/python3.12/site-packages/pypulseq
   add_gradients.py1376056%44, 52, 58, 61, 75–86, 92, 125–128, 135–136, 155, 162, 167–263
   add_ramps.py36360%1–92
   align.py35489%41, 45, 69, 73
   calc_duration.py33294%43, 50
   calc_ramp.py2202162%48–359
   calc_rf_bandwidth.py372824%45–81, 85–89
   check_timing.py962970%78, 82, 107, 180, 199, 232, 239, 249–293
   compress_shape.py30197%28
   convert.py40880%42, 48, 66, 72–73, 82, 88–89
   event_lib.py961485%6–9, 48–51, 70–71, 205–210
   make_adc.py981486%77, 80, 90–94, 97, 146, 149, 153, 159, 163, 202, 204, 206, 214
   make_adiabatic_pulse.py1323970%208–212, 232–236, 244–245, 268, 274, 343–362, 466–475, 513–521
   make_arbitrary_grad.py531572%71, 74, 77, 80, 96–98, 107, 109, 117–121, 130
   make_arbitrary_rf.py756316%95–179
   make_block_pulse.py48394%121–125, 128
   make_delay.py9189%27
   make_digital_output_pulse.py16288%42, 50
   make_extended_trapezoid.py561279%67, 70, 76, 82, 85, 88, 91, 94, 116, 134, 136, 139
   make_extended_trapezoid_area.py104496%68, 71, 261, 264
   make_gauss_pulse.py732073%142–146, 149–173, 180, 183
   make_label.py22482%64, 66, 68, 75
   make_sigpy_pulse.py1193075%12–13, 122, 125, 129, 166–170, 174, 177–178, 181–182, 197, 204, 209, 221, 224, 249–259, 273, 276, 306–316
   make_sinc_pulse.py701086%104, 110, 138–142, 146, 149–150, 153–154, 176
   make_soft_delay.py26292%107, 125
   make_trapezoid.py111794%177, 190, 196, 214, 232, 237, 255
   make_trigger.py16288%47, 55
   opts.py66986%78, 83, 102, 142, 166–170
   points_to_waveform.py9189%27
   rotate.py691480%15, 55, 66–69, 85–90, 112, 119–120
   scale_grad.py30197%65
   sigpy_pulse_opts.py26773%34–41
   split_gradient.py393121%46–103
   split_gradient_at.py702761%63–90, 110, 114, 118–120, 154–156
   traj_to_grad.py13931%26–40
   verify_file_signature.py582655%20, 29, 37, 41, 47, 54, 62–84
/home/runner/.local/lib/python3.12/site-packages/pypulseq/SAR
   SAR_calc.py5180%9
/home/runner/.local/lib/python3.12/site-packages/pypulseq/Sequence
   block.py4697983%63, 66, 74, 80, 95, 103, 109, 120, 123, 126, 134, 139, 148, 159, 167, 207, 209, 213, 225, 274, 278, 294, 319–345, 382–385, 421–429, 436, 466–470, 512, 518, 551, 587–594, 611, 621, 647, 685, 703, 706, 724, 738, 765, 844, 881, 905
   calc_grad_spectrum.py81766%68–190
   calc_pns.py403122%45–96
   ext_test_report.py1671193%79, 156, 167–168, 337–343
   install.py754244%31, 52, 69, 71, 112–131, 148, 181–184, 200–212, 254–278
   parula.py4250%19–86
   read_binary.py2407768%15, 20, 22, 24, 66, 72, 76, 78–79, 81–82, 84–85, 87–88, 95, 119, 125–132, 141, 147–159, 197–203, 228–229, 245–249, 253–258, 262–266, 270–285
   read_seq.py4003990%44–45, 110, 117, 130, 133–134, 138, 184, 228, 401, 422–439, 502, 505, 590, 614, 654, 685, 701–705, 712, 823, 834
   sequence.py65612880%10–13, 109–119, 140–153, 207, 272–275, 322, 366, 442, 469–474, 511, 539–542, 633, 661–670, 682, 704, 745–748, 802, 840, 851–852, 858, 869, 875, 877, 885, 918–926, 1058, 1173, 1179, 1182, 1185, 1222, 1347–1360, 1418, 1440–1442, 1463, 1526, 1534, 1632, 1643–1656, 1725–1726, 1737–1755, 1779, 1809–1817, 1853, 1867–1877
   write_binary.py1695766%25–28, 33–34, 39, 48, 70–77, 115–120, 123–131, 134–139, 142–147, 150–161, 185, 189
   write_seq.py362798%45, 77–79, 313, 347–349, 499
/home/runner/.local/lib/python3.12/site-packages/pypulseq/utils
   cumsum.py14193%17
   paper_plot.py63588%49–132
   safe_pns_prediction.py12611310%50–87, 102–189, 197–214, 222, 244–250, 279–286, 310–336, 344–383, 396–411, 415
   seq_plot.py40718654%22, 106, 138–139, 159, 176–178, 183–225, 235–242, 249–314, 318–324, 328–336, 359, 361, 363, 390–412, 457–458, 461–464, 487–496, 508, 525–535, 544–546, 565–567, 569–570, 572–573, 657–658, 674–691, 739–743
   tracing.py16662%33–34, 42, 54–55, 75
/home/runner/.local/lib/python3.12/site-packages/pypulseq/utils/siemens
   asc_to_hw.py58539%21–28, 48–106
   readasc.py59788%103–104, 110–111, 122–124
TOTAL5686172570% 

Tests Skipped Failures Errors Time
1289 24 💤 0 ❌ 0 🔥 4m 50s ⏱️

@mcencini

Copy link
Copy Markdown
Collaborator

Hi! Thanks for the PR. I noticed it fails on pre-commit, please make sure that the changes pass both pre-commit checks and tests before we start the review :)

@albertx-wang

Copy link
Copy Markdown
Author

Hi! Thanks for the note!

The pre-commit issue was due to a missing Path import in test_binary_signature.py, which has now been fixed.

I originally changed the text .seq [SIGNATURE] comment block to more closely match the MATLAB formatting. However, that caused many differences against the existing expected-output .seq files. To avoid unnecessary expected-output churn, I restored the existing .seq signature comment format while keeping the corrected MD5 computation behavior.

factor = float(_read_scalar(fid, np.float64))
hint_len = int(_read_scalar(fid, np.int32))
hint = fid.read(hint_len).decode('ascii')
if hint in seq.soft_delay_hint_ids:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason hint ID logic here is different from the read_seq() and requires extra variables stored?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference was to align with MATLAB’s binary read/write behavior. To avoid adding complexity here, I’ll move this into a separate PR.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. It was a conscious decision on our part to slightly deviate from Matlab Pulseq's logic. You can see the relevant discussion here #264. I think it is best to mimic read_seq() function to avoid any behavioral differences.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!

if 'Hash' in temp_sign_defs:
self.signature_value = temp_sign_defs['Hash']
self.signature_file = 'Text'
self.signature_file = 'text'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if any code out there uses this variable, but upstream Pulseq kept this as 'Text', so might be a better idea to keep it as 'Text', even though it is inconsistent capitalizaiton with 'bin'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this, I will keep it as Text for consistency!

@albertx-wang

Copy link
Copy Markdown
Author

Thanks for the feedback and discussion. After discussing this, we think it would be cleaner for us to first submit a separate PR that only addresses the signature issue. We’ll keep that PR focused on the minimal signature fix, and split the other features into separate follow-up PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants