From 20013c07db770e30d6424c10f2b2519e172d9a43 Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Tue, 4 Aug 2026 12:22:49 -0400 Subject: [PATCH 01/10] Add daily workflow to log GitHub traffic Introduces a new GitHub Actions workflow (`traffic_log.yml`) that runs daily (and manually) to snapshot repository traffic and release data. It checks out a dedicated logging repo, fetches clones/views/releases via `gh api`, stores date-stamped JSON files, and commits/pushes updates automatically. The workflow also adds concurrency controls and a 10-minute timeout. --- .github/workflows/traffic_log.yml | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/traffic_log.yml diff --git a/.github/workflows/traffic_log.yml b/.github/workflows/traffic_log.yml new file mode 100644 index 0000000..7be598f --- /dev/null +++ b/.github/workflows/traffic_log.yml @@ -0,0 +1,51 @@ +name: Log traffic stats + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + schedule: + - cron: "0 6 * * *" # daily at 06:00 UTC + workflow_dispatch: + +# Traffic endpoints require push access; bump to `write` if the fetch step +# starts returning 403s. +permissions: + contents: read + +jobs: + log-traffic: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout logging repo + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + repository: markmikkelsen/gannet-traffic-log + token: ${{ secrets.TRAFFIC_LOG_TOKEN }} + path: log-repo + + - name: Fetch traffic data + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + mkdir -p log-repo/clones log-repo/views log-repo/releases + DATE=$(date -u +%Y-%m-%d) + gh api "repos/${{ github.repository }}/traffic/clones" \ + > log-repo/clones/clones_$DATE.json + gh api "repos/${{ github.repository }}/traffic/views" \ + > log-repo/views/views_$DATE.json + gh api "repos/${{ github.repository }}/releases" \ + > log-repo/releases/releases_$DATE.json + + - name: Commit and push + working-directory: log-repo + run: | + set -euo pipefail + git config user.name "traffic-bot" + git config user.email "actions@github.com" + git add . + git diff --quiet --cached || git commit -m "Traffic snapshot $(date -u +%Y-%m-%d)" + git push From b33065152dfb89a4b232e4fa742dbbabd9529d97 Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Tue, 4 Aug 2026 12:28:10 -0400 Subject: [PATCH 02/10] Set traffic log workflow to contents write Update the `traffic_log` GitHub Actions workflow permissions from `contents: read` to `contents: write` so it has the repository access needed for traffic logging operations that may otherwise fail with 403 responses. --- .github/workflows/traffic_log.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/traffic_log.yml b/.github/workflows/traffic_log.yml index 7be598f..a8aac76 100644 --- a/.github/workflows/traffic_log.yml +++ b/.github/workflows/traffic_log.yml @@ -9,10 +9,8 @@ on: - cron: "0 6 * * *" # daily at 06:00 UTC workflow_dispatch: -# Traffic endpoints require push access; bump to `write` if the fetch step -# starts returning 403s. permissions: - contents: read + contents: write jobs: log-traffic: From a63cbdd077b1749e2eddbda0858e30083badd038 Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Tue, 4 Aug 2026 12:33:09 -0400 Subject: [PATCH 03/10] Use dedicated token for traffic log fetch Tightens the traffic logging workflow by reducing repository permissions from `contents: write` to `contents: read` and switching API auth from the default `GITHUB_TOKEN` to `TRAFFIC_LOG_TOKEN`. This aligns the job with least-privilege access while using a purpose-specific credential for fetching traffic data. --- .github/workflows/traffic_log.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/traffic_log.yml b/.github/workflows/traffic_log.yml index a8aac76..364ef1b 100644 --- a/.github/workflows/traffic_log.yml +++ b/.github/workflows/traffic_log.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: permissions: - contents: write + contents: read jobs: log-traffic: @@ -26,7 +26,7 @@ jobs: - name: Fetch traffic data env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.TRAFFIC_LOG_TOKEN }} run: | set -euo pipefail mkdir -p log-repo/clones log-repo/views log-repo/releases From 518c0068b486a437cbef2c3dcf5d82687b24be9f Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Tue, 4 Aug 2026 12:34:09 -0400 Subject: [PATCH 04/10] Grant write access in traffic log workflow --- .github/workflows/traffic_log.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/traffic_log.yml b/.github/workflows/traffic_log.yml index 364ef1b..5d45cc1 100644 --- a/.github/workflows/traffic_log.yml +++ b/.github/workflows/traffic_log.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: permissions: - contents: read + contents: write jobs: log-traffic: From da3c9a8fd31dd23b0b8db611612a0101db736df3 Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Tue, 4 Aug 2026 16:27:00 -0400 Subject: [PATCH 05/10] Update traffic_log.yml --- .github/workflows/traffic_log.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/traffic_log.yml b/.github/workflows/traffic_log.yml index 5d45cc1..c78d622 100644 --- a/.github/workflows/traffic_log.yml +++ b/.github/workflows/traffic_log.yml @@ -4,9 +4,11 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +run-name: ${{ github.event_name }} - logging traffic stats + on: schedule: - - cron: "0 6 * * *" # daily at 06:00 UTC + - cron: 0 6 * * * # daily at 06:00 UTC workflow_dispatch: permissions: From 58f850a6eb43808cefb19313c574f21cbd121b96 Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Fri, 14 Aug 2026 08:20:01 -0400 Subject: [PATCH 06/10] Add support for GE header revision 31 Extend GE P-file revision checks and header mappings to recognize revision 31 across de-identification, reading, and mask generation paths. Also update the unsupported-revision assertion message to direct users to the GitHub issues page for help. --- GEDeIdentify.m | 6 ++++-- GERead.m | 8 +++++--- GannetMask_GE.m | 2 +- GannetMask_GE_nii.m | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/GEDeIdentify.m b/GEDeIdentify.m index 959515a..33b57fa 100644 --- a/GEDeIdentify.m +++ b/GEDeIdentify.m @@ -234,7 +234,8 @@ function GEDeIdentify(fnames) fseek(pfile_fid, 1500, 'bof'); hdr.series_offset = fread(pfile_fid, 1, 'integer*4'); elseif rdbm_rev_num > 11.0 - chkRev = {'14.3','16','20.006','20.007','24','26.002','27','27.001','28.002','28.003','30','30.1'}; + chkRev = {'14.3','16','20.006','20.007','24','26.002','27', ... + '27.001','28.002','28.003','30','30.1','31'}; if ~any(strcmp(num2str(rdbm_rev_num), chkRev)) fclose(pfile_fid); fclose(pfile_fid_noID); @@ -254,7 +255,8 @@ function GEDeIdentify(fnames) rdb_hdr_da_yres = 53; rdb_hdr_dab_start_rcv = 101; rdb_hdr_dab_stop_rcv = 102; - case {'26.002','27','27.001','28.002','28.003','30','30.1'} + case {'26.002','27','27.001','28.002','28.003','30','30.1', ... + '31'} rdb_hdr_off_image = 11; rdb_hdr_off_data = 2; rdb_hdr_off_exam = 9; diff --git a/GERead.m b/GERead.m index f45bf21..09d7cb5 100644 --- a/GERead.m +++ b/GERead.m @@ -36,10 +36,12 @@ end MRS_struct.p.GE.rdbm_rev_num(ii) = rdbm_rev_num; -chkRev = {'14.3','16','20.006','20.007','24','26.002','27','27.001','28.002','28.003','30','30.1'}; +chkRev = {'14.3','16','20.006','20.007','24','26.002','27','27.001', ... + '28.002','28.003','30','30.1','31'}; assert(any(strcmp(num2str(rdbm_rev_num), chkRev)), ... sprintf(['GERead.m is not fully functional with P-file header revision number %g. ' ... - 'Please contact the Gannet developers (mam4041@med.cornell.edu) for assistance.'], ... + 'Please open an issue on the Gannet GitHub repo ' ... + 'for assistance: https://github.com/markmikkelsen/Gannet/issues'], ... rdbm_rev_num)); % RTN 2018 @@ -175,7 +177,7 @@ image_user22 = 112; image_user24 = 116; - case {'26.002','27','27.001','28.002','28.003','30','30.1'} + case {'26.002','27','27.001','28.002','28.003','30','30.1','31'} % int rdb_hdr_off_image = 11; diff --git a/GannetMask_GE.m b/GannetMask_GE.m index 36ab9af..c81a7a7 100644 --- a/GannetMask_GE.m +++ b/GannetMask_GE.m @@ -31,7 +31,7 @@ tlhc = 181; trhc = 184; brhc = 187; - case {'26.002','27','27.001','28.002','28.003','30','30.1'} + case {'26.002','27','27.001','28.002','28.003','30','30.1','31'} rdb_hdr_off_image = 11; rdb_hdr_ps_mps_freq = 123; tlhc = 181; diff --git a/GannetMask_GE_nii.m b/GannetMask_GE_nii.m index a1bdac6..01e49aa 100644 --- a/GannetMask_GE_nii.m +++ b/GannetMask_GE_nii.m @@ -30,7 +30,7 @@ tlhc = 181; trhc = 184; brhc = 187; - case {'26.002','27','27.001','28.002','28.003','30','30.1'} + case {'26.002','27','27.001','28.002','28.003','30','30.1','31'} rdb_hdr_off_image = 11; rdb_hdr_ps_mps_freq = 123; tlhc = 181; From 43723c89a95a26d059a0151ca9f699a0db262079 Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Fri, 14 Aug 2026 12:28:36 -0400 Subject: [PATCH 07/10] Add CMRR echo-point fallback in TWIX reader Updates `SiemensTWIXRead.m` to read `pointsBeforeEcho` from `iceParam(5,1)` for CMRR MEGA sequences, with a fallback to `freeParam(1)` if that field is unavailable. This replaces sequence-type branching with more robust parameter handling across CMRR MEGA-PRESS and MEGA-sLASER variants. --- GannetLoad.m | 2 +- SiemensTWIXRead.m | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/GannetLoad.m b/GannetLoad.m index e34b9bd..d9a5c56 100644 --- a/GannetLoad.m +++ b/GannetLoad.m @@ -21,7 +21,7 @@ MRS_struct.info.datetime.load = datetime('now'); MRS_struct.info.version.Gannet = '3.5.4'; -MRS_struct.info.version.load = '260618'; +MRS_struct.info.version.load = '260814'; MRS_struct.p.bids = 0; VersionCheck(0, MRS_struct.info.version.Gannet); ToolboxCheck; diff --git a/SiemensTWIXRead.m b/SiemensTWIXRead.m index c9c630d..795ba13 100644 --- a/SiemensTWIXRead.m +++ b/SiemensTWIXRead.m @@ -415,11 +415,10 @@ % echo maximum in different fields, depending on the origin of the % sequence: if strcmp(TwixHeader.seqorig,'CMRR') - if strcmp(TwixHeader.seqtype,'MEGA-PRESS') + try TwixHeader.pointsBeforeEcho = twix_obj.image.iceParam(5,1); - elseif strcmp(TwixHeader.seqtype,'MEGA-sLASER') % SH 20191213 - % TwixHeader.pointsBeforeEcho = twix_obj.image.freeParam(1); - TwixHeader.pointsBeforeEcho = twix_obj.image.iceParam(5,1); % MM (250805): This parameter may actually be the same as for the CMRR MEGA-PRESS WIP + catch + TwixHeader.pointsBeforeEcho = twix_obj.image.freeParam(1); end elseif strcmp(TwixHeader.seqorig,'WIP') % Siemens WIP TwixHeader.pointsBeforeEcho = twix_obj.image.cutOff(1,1); From cb5b61e86b117cc2b0c9f4ec10d7ada7f0366deb Mon Sep 17 00:00:00 2001 From: markmikkelsen <6051303+markmikkelsen@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:10:35 +0000 Subject: [PATCH 08/10] Sync with dependencies repos --- export_fig/crop_borders.m | 8 +++++++- export_fig/export_fig.m | 19 ++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/export_fig/crop_borders.m b/export_fig/crop_borders.m index 586c042..f039eef 100644 --- a/export_fig/crop_borders.m +++ b/export_fig/crop_borders.m @@ -26,6 +26,7 @@ % 23/10/16: Fixed issue #175: there used to be a 1px minimal padding in case of crop, now removed % 15/05/22: Fixed EPS bounding box (issue #356) % 26/07/26: Fixed left edge auto-crop in Matlab R2025a+ (issue #408) +% 13/08/26: Fixed bug when using positive (extra) padding parameter (PR #416) %} if nargin < 3 @@ -142,7 +143,12 @@ if padding > 0 % extra padding % Create an empty image, containing the background color, that has the % cropped image size plus the padded border - B = repmat(bcol,[(b-t)+1+padding*2,(r-l)+1+padding*2,1,n]); % Fix per Luiz Carvalho + nRows = b-t+1 + padding*2; + nCols = r-l+1 + padding*2; + %B = repmat(bcol, [nRows,nCols,1,n]); % Fix per Luiz Carvalho + B(1:nRows, 1:nCols, 3, 1:n) = bcol(3); % Fix PR #416 + B(1:nRows, 1:nCols, 2, 1:n) = bcol(2); % Fix PR #416 + B(1:nRows, 1:nCols, 1, 1:n) = bcol(1); % Fix PR #416 % vA - coordinates in A that contain the cropped image vA = [t b l r]; % vB - coordinates in B where the cropped version of A will be placed diff --git a/export_fig/export_fig.m b/export_fig/export_fig.m index 75ee853..67a56d0 100644 --- a/export_fig/export_fig.m +++ b/export_fig/export_fig.m @@ -419,6 +419,7 @@ % 01/06/26: (3.55) Fixes for exporting uifigures in Matlab R2025a+ (issue #408) % 22/07/26: (3.56) Suppress some warnings/alerts if -silent option specified or in parallel/batch mode (issue #415); added default comment in GIF/JPG/PNG/TIF images; try to create export folder if it does not exist % 26/07/26: (3.57) Fixed unintended figure undock, bgcolor and bad auto-cropping in Matlab R2025a+ (issue #408) +% 13/08/26: (3.58) Fixed TIF export bug; fixed bug when using -p (extra padding/cropping) parameter; improved parsing of parameter-value input pairs %} if nargout @@ -457,7 +458,7 @@ [fig, options] = parse_args(nargout, fig, argNames, varargin{:}); % Check for newer version and exportgraphics/copygraphics compatibility - currentVersion = 3.57; + currentVersion = 3.58; if options.version % export_fig's version requested - return it and bail out imageData = currentVersion; return @@ -1023,7 +1024,7 @@ format_options = getFormatOptions(options, 'tif'); %Issue #269 default_options = {'Resolution',resolution, 'WriteMode',mode, ... 'Description',createdUsing}; - imwrite(img, filename, default_options, format_options{:}); + imwrite(img, filename, default_options{:}, format_options{:}); end if options.notify, notify(filename); end end @@ -2106,12 +2107,16 @@ function notify(filename) case 's', options.fixed_size = vals; end else % scalar parameter value - val = str2double(regexpi(thisArg, '(?<=-([mrqp]))-?\d*.?\d+', 'match')); + val = str2double(regexpi(thisArg, '(?<=-([mrqp]))\s*-?\d*.?\d+', 'match')); if isempty(val) || isnan(val) - % Issue #51: improved processing of input args (accept space between param name & value) - val = str2double(varargin{a+1}); - if isscalar(val) && ~isnan(val) - skipNext = 1; + if ~isempty(extra) + val = str2double(extra(2:end)); + else + % Issue #51: improved processing of input args (accept space between param name & value) + val = str2double(varargin{a+1}); + if isscalar(val) && ~isnan(val) + skipNext = 1; + end end end if ~isscalar(val) || isnan(val) From 6a4dbc618a56d2a1b8a81998ccd57f6a2a95a295 Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Tue, 18 Aug 2026 12:09:14 -0400 Subject: [PATCH 09/10] Update update_dependencies.yml --- .github/workflows/update_dependencies.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update_dependencies.yml b/.github/workflows/update_dependencies.yml index ac850c9..a497986 100644 --- a/.github/workflows/update_dependencies.yml +++ b/.github/workflows/update_dependencies.yml @@ -1,10 +1,10 @@ -name: Update dependencies - bids-matlab, dicm2nii, etc. +name: Update dependencies concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -run-name: ${{ github.event_name }} - ${{ github.actor }} is updating dependencies +run-name: ${{ github.event_name }} - updating dependencies on: schedule: From 9bed4229f8572c004c7c9e00d284de4a1c032a2c Mon Sep 17 00:00:00 2001 From: Mark Mikkelsen Date: Mon, 24 Aug 2026 14:14:23 -0400 Subject: [PATCH 10/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95afa33..41a3ac6 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Gannet uses a form of [semantic versioning](https://semver.org/) in the style 'x ## Lead developer -- Mark Mikkelsen (Weill Cornell Medicine) +- Mark Mikkelsen ## Creator