Skip to content

Commit 01ed642

Browse files
committed
Move to using a foyer package to install SimpleITK
Previous CRAN-like structure was not working with standard R install tools. The File specification in the PACKAGES file is appended to the CRAN-like's URL which works if the actual package is on the website but specifying a URL as the File value results in an invalid URL structure (https://cran_likehttps://file_location). Moving to a foyer package approach supports download from arbitrary location. The user installs the SimpleITK foyer package using standard R tools (mimicing a SimpleITK install). Then calls the library(SimpleITK); install_simpleitk() which downloads and installs SimpleITK itself, overwritting the foyer package. Next call to library(SimpleITK) loads the actual SimpleITK package.
1 parent e27322b commit 01ed642

11 files changed

Lines changed: 510 additions & 236 deletions

File tree

.github/workflows/main.yml

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -177,38 +177,35 @@ jobs:
177177
178178
Detailed release notes are available on the [main SimpleITK repository](https://github.com/SimpleITK/SimpleITK/releases/${{ github.ref_name }}).
179179
180-
The GitHub Pages repository provides platform-specific binaries. If a binary is not available, you will need to build SimpleITK locally using the remotes installer. See the [README](https://github.com/${{ github.repository }}/blob/main/README.md) for detailed instructions.
180+
To install SimpleITK we use a Foyer helper package that downloads the appropriate binary from the GitHub release assets. This is a two step process.
181181
182-
Windows and macOS:
182+
To install the latest SimpleITK version to your primary library directory, first element of `.libPaths()`, run the following:
183183
184184
```r
185+
# install the SimpleITK foyer package
185186
install.packages(
186-
"SimpleITK",
187-
repos = c("https://simpleitk.github.io/SimpleITKRInstaller/${{ github.ref_name }}"),
188-
type = "binary"
187+
"SimpleITK.foyer",
188+
repos = c("https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"),
189+
type = "source"
189190
)
190-
```
191191
192-
Linux:
192+
# Use foyer to install SimpleITK
193+
library(SimpleITK.foyer)
194+
install_simpleitk()
195+
```
196+
Now you can load the SimpleITK library as usual:
193197
194198
```r
195-
install.packages(
196-
"SimpleITK",
197-
repos = NULL,
198-
contriburl = paste0(
199-
"https://simpleitk.github.io/SimpleITKRInstaller/${{ github.ref_name }}/__linux__/ubuntu-noble/",
200-
paste(R.version$major, sub("\\..*$", "", R.version$minor), sep = "."),
201-
"/src/contrib"
202-
),
203-
type = "source"
204-
)
199+
library(SimpleITK)
205200
```
206201
207-
The Linux install above uses a "source" type installation even though it is binary, due to the way R handles binary packages on this platform.
208-
209-
If for some reason you decided to download the package artifact from this release page, before you install the package you will need to first unzip the file. Then rename it to `SimpleITK_<version>.zip` (windows), `SimpleITK_<version>.tgz` (macOS), or `SimpleITK_<version>.tar.gz` (Linux) to match the expected file name format.
202+
The `install_simpleitk` function provides finer installation control such as SimpleITK version, library installation location and more. To see all options:
210203
211204
```r
205+
help(install_simpleitk)
206+
```
207+
208+
If you directly download the package artifact from this release page, before you install the package you will need to first unzip the file. Then rename it to `SimpleITK_<version>.zip` (windows), `SimpleITK_<version>.tgz` (macOS), or `SimpleITK_<version>.tar.gz` (Linux) to match the expected file name format.
212209
213210
files: |
214211
release-artifacts/*

.github/workflows/update_gh_cran.yml

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,23 @@ jobs:
2424
- name: Set up R
2525
uses: r-lib/actions/setup-r@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2.12.0
2626

27-
- name: Download Release Assets
28-
env:
29-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27+
- name: Build Foyer Package
3028
run: |
31-
mkdir -p temp_packages
32-
gh release download ${{ github.event.release.tag_name }} --dir temp_packages
29+
Rscript update_cran_repo.R \
30+
--foyer_dir SimpleITK_Foyer \
31+
--output_dir /tmp/cran_output \
32+
--repo_url https://github.com/${{ github.repository }} \
33+
--tag ${{ github.event.release.tag_name }}
3334
34-
- name: Route Assets and Build PACKAGES Files
35-
shell: bash
36-
run: |
37-
# Run the R script and always do the cleanup of the temp_packages directory,
38-
# step returns the exit code from the R script.
39-
(
40-
Rscript update_cran_repo.R \
41-
--packages_dir temp_packages \
42-
--base_cran_dir . \
43-
--repo_url https://github.com/${{ github.repository }} \
44-
--tag ${{ github.event.release.tag_name }}
45-
status=$?
46-
rm -rf temp_packages
47-
exit $status
48-
)
49-
50-
- name: Commit and Push Changes
35+
- name: Deploy to gh-pages
5136
run: |
5237
git config --global user.name "github-actions[bot]"
5338
git config --global user.email "github-actions[bot]@users.noreply.github.com"
5439
git checkout gh-pages
55-
git add .
40+
rm -rf src
41+
cp -r /tmp/cran_output/src .
42+
git add src
5643
if ! git diff --cached --quiet; then
57-
git commit -m "Update CRAN repository index for release ${{ github.event.release.tag_name }}"
44+
git commit -m "Update CRAN-like repository for release ${{ github.event.release.tag_name }}"
5845
git push
5946
fi

SimpleITK_Foyer/DESCRIPTION

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Package: SimpleITK.foyer
2+
Version: 0.0.0
3+
Title: Lightweight Installer for SimpleITK
4+
Authors@R: c(person("Richard", "Beare", role = "aut",
5+
email = "Richard.Beare@ieee.org"),
6+
person("Bradley", "Lowekamp", role = "aut",
7+
email = "blowekamp@mail.nih.gov"),
8+
person("Ziv", "Yaniv", role = c("aut", "cre"),
9+
email = "zivyaniv@nih.gov"))
10+
Description: Provides a lightweight installer for the SimpleITK package, which
11+
is an interface to the Insight Toolkit (ITK) for medical image segmentation
12+
and registration. After installing this package, call install_simpleitk().
13+
to download and install the pre-built binary packages for your platform
14+
from the SimpleITKRInstaller GitHub releases. Due to the large size of the compiled
15+
package, the actual functionality is distributed as platform-specific binaries
16+
on GitHub.
17+
License: Apache License (>= 2)
18+
URL: https://github.com/SimpleITK/SimpleITKRInstaller
19+
BugReports: https://github.com/SimpleITK/SimpleITKRInstaller/issues
20+
Encoding: UTF-8
21+
Depends: R (>= 4.0)
22+
Imports: utils
23+
SystemRequirements: Internet connection for downloading binary packages
24+
Maintainer: Ziv Yaniv <zivyaniv@nih.gov>
25+
Config/roxygen2/version: 8.0.0

SimpleITK_Foyer/NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export(install_simpleitk)
2+
importFrom(utils, install.packages)
3+
importFrom(utils, download.file)

SimpleITK_Foyer/R/install.R

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Base URLs for SimpleITK binary releases.
2+
# These values are updated by the build workflow when generating the foyer package.
3+
.sitk_releases_base_url <- "https://github.com/SimpleITK/SimpleITKRInstaller/releases/download"
4+
.sitk_releases_page_url <- "https://github.com/SimpleITK/SimpleITKRInstaller/releases"
5+
.sitk_repo_url <- "https://github.com/SimpleITK/SimpleITKRInstaller"
6+
7+
#' Install SimpleITK Binary Package
8+
#'
9+
#' Downloads and installs the pre-built SimpleITK binary package for your
10+
#' platform and R version from GitHub releases.
11+
#'
12+
#' @param version Character string specifying the SimpleITK version to install.
13+
#' Defaults to the version of this foyer package.
14+
#' @param lib Character string specifying the library path where the package
15+
#' should be installed. Defaults to the first element of \code{.libPaths()}.
16+
#' @param repos Character string specifying alternative repository URL.
17+
#' By default, uses GitHub releases.
18+
#' @param force Logical. If \code{TRUE}, forces reinstallation even if the
19+
#' package is already installed.
20+
#' @param quiet Logical. If \code{TRUE}, suppresses progress messages.
21+
#'
22+
#' @return Invisibly returns \code{TRUE} if installation succeeds,
23+
#' \code{FALSE} otherwise.
24+
#'
25+
#' @details
26+
#' This function detects your operating system, R version, and architecture,
27+
#' then downloads the appropriate pre-built binary package from the SimpleITK
28+
#' GitHub releases. The binary packages are built by the SimpleITK project
29+
#' and hosted on GitHub releases.
30+
#'
31+
#' Supported platforms:
32+
#' \itemize{
33+
#' \item Windows x86_64 (R >= 4.0)
34+
#' \item macOS x86_64 (R >= 4.0)
35+
#' \item macOS ARM64 (R >= 4.0)
36+
#' \item Linux x86_64 (R >= 4.0)
37+
#' }
38+
#'
39+
#' @examples
40+
#' \dontrun{
41+
#' # Install SimpleITK for your platform
42+
#' install_simpleitk()
43+
#'
44+
#' # Install a specific version
45+
#' install_simpleitk(version = "2.5.0")
46+
#'
47+
#' # Force reinstallation
48+
#' install_simpleitk(force = TRUE)
49+
#' }
50+
#'
51+
#' @export
52+
install_simpleitk <- function(version = NULL,
53+
lib = .libPaths()[1],
54+
repos = NULL,
55+
force = FALSE,
56+
quiet = FALSE) {
57+
58+
# Check if already installed
59+
if (!force && requireNamespace("SimpleITK", quietly = TRUE)) {
60+
if (!quiet) {
61+
message("SimpleITK is already installed. Use force = TRUE to reinstall.")
62+
}
63+
return(invisible(TRUE))
64+
}
65+
66+
# Get version from DESCRIPTION if not specified
67+
if (is.null(version)) {
68+
version <- utils::packageDescription("SimpleITK.foyer", fields = "Version")
69+
if (is.na(version)) {
70+
stop("Cannot determine SimpleITK version. Please specify version parameter.")
71+
}
72+
}
73+
74+
# Get R version (major.minor only)
75+
r_version <- paste(R.version$major,
76+
strsplit(R.version$minor, "\\.")[[1]][1],
77+
sep = ".")
78+
79+
# Detect platform
80+
platform_info <- get_platform_info()
81+
82+
if (is.null(platform_info)) {
83+
stop("Unsupported platform. SimpleITK binaries are only available for ",
84+
"Windows (x86_64), macOS (x86_64, ARM64), and Linux (x86_64).")
85+
}
86+
87+
# Construct download URL
88+
if (is.null(repos)) {
89+
tag <- paste0("v", version)
90+
filename <- sprintf("SimpleITK_%s_R%s_%s.%s",
91+
version, r_version,
92+
platform_info$platform,
93+
platform_info$extension)
94+
download_url <- file.path(.sitk_releases_base_url, tag, filename)
95+
} else {
96+
download_url <- file.path(repos, sprintf("SimpleITK_%s.%s",
97+
version,
98+
platform_info$extension))
99+
}
100+
101+
if (!quiet) {
102+
message("Downloading SimpleITK ", version, " for ", platform_info$platform,
103+
" (R ", r_version, ")...")
104+
message("URL: ", download_url)
105+
}
106+
107+
# Create temporary directory and file with proper naming
108+
# R expects package files to be named: PackageName_Version.extension
109+
temp_dir <- tempfile()
110+
dir.create(temp_dir, showWarnings = FALSE)
111+
on.exit(unlink(temp_dir, recursive = TRUE), add = TRUE)
112+
113+
pkg_filename <- sprintf("SimpleITK_%s.%s", version, platform_info$extension)
114+
temp_file <- file.path(temp_dir, pkg_filename)
115+
116+
# Download the binary
117+
tryCatch({
118+
download.file(download_url, temp_file, mode = "wb", quiet = quiet)
119+
}, error = function(e) {
120+
stop("Failed to download SimpleITK binary package.\n",
121+
"URL: ", download_url, "\n",
122+
"Error: ", conditionMessage(e), "\n",
123+
"Please check that:\n",
124+
" 1. You have an internet connection\n",
125+
" 2. The specified version (", version, ") has pre-built binaries\n",
126+
" 3. A binary exists for your platform and R version\n",
127+
"Available releases: ", .sitk_releases_page_url)
128+
})
129+
130+
if (!quiet) {
131+
message("Installing SimpleITK package...")
132+
}
133+
134+
# Install the binary package
135+
tryCatch({
136+
install.packages(temp_file, repos = NULL, type = "source", lib = lib,
137+
quiet = quiet)
138+
139+
if (!quiet) {
140+
message("SimpleITK successfully installed!")
141+
message("Load it with: library(SimpleITK)")
142+
}
143+
return(invisible(TRUE))
144+
145+
}, error = function(e) {
146+
stop("Failed to install SimpleITK binary package.\n",
147+
"Error: ", conditionMessage(e), "\n",
148+
"You may need to build from source instead, see ", .sitk_repo_url, ".\n")
149+
})
150+
}
151+
152+
153+
#' Get Platform Information
154+
#'
155+
#' @return A list with platform and extension, or NULL if unsupported
156+
#' @keywords internal
157+
get_platform_info <- function() {
158+
os <- Sys.info()["sysname"]
159+
arch <- Sys.info()["machine"]
160+
161+
# Support both x86_64 and ARM64 architectures
162+
# Note: Windows may report "x86-64" (with hyphen) or "x86_64" (with underscore)
163+
is_x86_64 <- grepl("x86[_-]64|amd64", arch, ignore.case = TRUE)
164+
is_arm64 <- grepl("arm64|aarch64", arch, ignore.case = TRUE)
165+
166+
if (!is_x86_64 && !is_arm64) {
167+
return(NULL)
168+
}
169+
170+
if (os == "Windows") {
171+
return(list(platform = "windows-x86_64", extension = "zip"))
172+
} else if (os == "Darwin") {
173+
# macOS: distinguish between x86_64 and ARM64
174+
if (is_arm64) {
175+
return(list(platform = "macos-arm64", extension = "tgz"))
176+
} else {
177+
return(list(platform = "macos-x86_64", extension = "tgz"))
178+
}
179+
} else if (os == "Linux") {
180+
return(list(platform = "linux-x86_64", extension = "tar.gz"))
181+
} else {
182+
return(NULL)
183+
}
184+
}

SimpleITK_Foyer/R/zzz.R

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#' @keywords internal
2+
.onAttach <- function(libname, pkgname) {
3+
# Check if the full SimpleITK binary is installed
4+
full_pkg_installed <- tryCatch({
5+
# Check if the package exists and has the actual SimpleITK functionality
6+
pkg_path <- system.file(package = "SimpleITK", lib.loc = libname)
7+
if (pkg_path != "") {
8+
# Check if it has the core SimpleITK functions (not just this foyer)
9+
ns <- loadNamespace("SimpleITK")
10+
exists("Image", envir = ns, mode = "function")
11+
} else {
12+
FALSE
13+
}
14+
}, error = function(e) FALSE)
15+
16+
if (!full_pkg_installed) {
17+
packageStartupMessage(
18+
"================================================================================\n",
19+
"Welcome to SimpleITK installer!\n\n",
20+
"The SimpleITK package is not yet installed.\n",
21+
"To download and install the latest pre-built binary for your platform, run:\n\n",
22+
" install_simpleitk()\n\n",
23+
"This will download a platform-specific binary from GitHub releases.\n",
24+
"After installation, load the package with: library(SimpleITK)\n",
25+
"To download and install a specific SimpleITK version, use: install_simpleitk(version = 'x.y.z')\n",
26+
"================================================================================"
27+
)
28+
}
29+
}

SimpleITK_Foyer/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# SimpleITK Foyer Package
2+
3+
This is a lightweight installer package for SimpleITK, providing easy access to pre-built SimpleITK binaries for R.
4+
5+
## Installation
6+
7+
Install the SimpleITK foyer package from GitHub Pages:
8+
```r
9+
install.packages("SimpleITK.foyer",
10+
repos = "https://SimpleITK.github.io/SimpleITKRInstaller",
11+
type = "source")
12+
```
13+
14+
Then download and install the **latest** binary package:
15+
```r
16+
library(SimpleITK.foyer)
17+
install_simpleitk()
18+
```
19+
20+
or a specific version:
21+
```r
22+
library(SimpleITK.foyer)
23+
install_simpleitk(version = "2.5.5")
24+
```
25+
26+
Old versions may not be available, previously not distributed as binaries. In this case you will need to build SimpleITK locally [using the SimpleITKRInstaller](https://github.com/SimpleITK/SimpleITKRInstaller).
27+
28+
After installtion, use SimpleITK as usual:
29+
30+
```r
31+
library(SimpleITK)
32+
33+
img <- ReadImage("path/to/image.dcm")
34+
```
35+
36+
## Rational for using Foyer Package
37+
38+
The full SimpleITK package is quite large (~40-50 MB depending on platform) and requires significant compilation time when built from source. To make it easier for users, we distribute:
39+
40+
1. **This lightweight foyer package** - hosted on GitHub Pages as a CRAN-like repository
41+
2. **Pre-built binaries on GitHub Releases** - the actual SimpleITK package
42+
43+
The foyer package installs instantly and provides the `install_simpleitk()` function which downloads the correct pre-built binary for your platform.

0 commit comments

Comments
 (0)