Comprehensive real-world examples demonstrating how to use JLSwift for JPEG-LS compression in various scenarios.
- Basic Examples
- Advanced Examples
- Performance Optimisation Examples
- Error Handling Examples
- Command-Line Tool Examples
- Non-DICOM Usage Examples
Encode a simple 8-bit greyscale image to JPEG-LS format:
import JPEGLS
// Example: Encoding a simple gradient image
func encodeGrayscaleImage() throws {
// Create a simple 256×256 gradient image
let width = 256
let height = 256
var pixels: [[Int]] = []
for y in 0..<height {
var row: [Int] = []
for x in 0..<width {
// Create a diagonal gradient
let value = (x + y) % 256
row.append(value)
}
pixels.append(row)
}
// Create greyscale image data
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
// Encode using the high-level encoder (lossless, no interleaving)
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print("Encoded \(width * height) pixels")
print("Output size: \(jpegLSData.count) bytes")
}
try encodeGrayscaleImage()Encode a full-color RGB image with sample interleaving:
import JPEGLS
func encodeRGBImage() throws {
let width = 512
let height = 512
// Create RGB test pattern (red gradient, green gradient, blue constant)
var red: [[Int]] = []
var green: [[Int]] = []
var blue: [[Int]] = []
for y in 0..<height {
var redRow: [Int] = []
var greenRow: [Int] = []
var blueRow: [Int] = []
for x in 0..<width {
redRow.append((x * 255) / width)
greenRow.append((y * 255) / height)
blueRow.append(128)
}
red.append(redRow)
green.append(greenRow)
blue.append(blueRow)
}
// Create RGB image data
let imageData = try MultiComponentImageData.rgb(
redPixels: red,
greenPixels: green,
bluePixels: blue,
bitsPerSample: 8
)
// Encode using the high-level encoder (lossless, sample-interleaved)
let encoder = JPEGLSEncoder()
let config = try JPEGLSEncoder.Configuration(
near: 0,
interleaveMode: .sample
)
let jpegLSData = try encoder.encode(imageData, configuration: config)
print("Encoded RGB image: \(width)×\(height)")
print("Output size: \(jpegLSData.count) bytes")
print("Interleave mode: sample-interleaved")
}
try encodeRGBImage()Use near-lossless compression for higher compression ratios with controlled quality loss:
import JPEGLS
func encodeNearLossless() throws {
// Load or create your image data
let width = 512
let height = 512
let pixels = createTestImage(width: width, height: height)
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
// Encode with near-lossless (NEAR=3 allows maximum error of ±3 grey levels)
let encoder = JPEGLSEncoder()
let config = try JPEGLSEncoder.Configuration(near: 3)
let jpegLSData = try encoder.encode(imageData, configuration: config)
print("Near-lossless encoding (NEAR=3)")
print("Output size: \(jpegLSData.count) bytes")
print("Expected better compression than lossless with minimal quality loss")
}
func createTestImage(width: Int, height: Int) -> [[Int]] {
var pixels: [[Int]] = []
for y in 0..<height {
var row: [Int] = []
for x in 0..<width {
// Checkerboard pattern
let value = ((x / 8) + (y / 8)) % 2 == 0 ? 200 : 50
row.append(value)
}
pixels.append(row)
}
return pixels
}
try encodeNearLossless()Parse and decode JPEG-LS files:
import JPEGLS
import Foundation
func decodeJPEGLSFile(from path: String) throws {
// Read the JPEG-LS file
let data = try Data(contentsOf: URL(fileURLWithPath: path))
// Decode using the high-level decoder
let decoder = JPEGLSDecoder()
let imageData = try decoder.decode(data)
print("File decoded successfully:")
print(" Dimensions: \(imageData.frameHeader.width)×\(imageData.frameHeader.height)")
print(" Bits per sample: \(imageData.frameHeader.bitsPerSample)")
print(" Components: \(imageData.frameHeader.componentCount)")
// Access pixel data
for (index, component) in imageData.components.enumerated() {
print(" Component \(index + 1): \(component.pixels.count) rows × \(component.pixels.first?.count ?? 0) columns")
}
}
// Example usage
try decodeJPEGLSFile(from: "medical_image.jls")Encode an image, decode it, and verify pixel-exact round-trip:
import JPEGLS
/// Encodes image data to JPEG-LS, decodes it back, and verifies pixel equality.
func roundTripVerify() throws {
let width = 128
let height = 128
// Build a gradient test image
let pixels: [[Int]] = (0..<height).map { y in
(0..<width).map { x in (x + y) % 256 }
}
// --- Encode ---
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print("Encoded: \(jpegLSData.count) bytes (original: \(width * height) bytes)")
// --- Decode ---
let decoder = JPEGLSDecoder()
let decoded = try decoder.decode(jpegLSData)
// --- Verify ---
let decodedPixels = decoded.components[0].pixels
var mismatch = 0
for y in 0..<height {
for x in 0..<width {
if decodedPixels[y][x] != pixels[y][x] {
mismatch += 1
}
}
}
if mismatch == 0 {
print("✓ Lossless round-trip verified — all \(width * height) pixels match exactly.")
} else {
print("✗ \(mismatch) pixel(s) differ after round-trip.")
}
}
try roundTripVerify()Near-lossless round-trip with bounded error verification:
import JPEGLS
func roundTripNearLossless(near: Int = 3) throws {
let pixels: [[Int]] = (0..<64).map { y in (0..<64).map { x in x * 4 } }
let imageData = try MultiComponentImageData.grayscale(pixels: pixels, bitsPerSample: 8)
let config = try JPEGLSEncoder.Configuration(near: near, interleaveMode: .none)
let encoded = try JPEGLSEncoder().encode(imageData, configuration: config)
let decoded = try JPEGLSDecoder().decode(encoded)
let decodedPixels = decoded.components[0].pixels
let maxError = (0..<64).flatMap { y in (0..<64).map { x in
abs(decodedPixels[y][x] - pixels[y][x])
}}.max() ?? 0
print("NEAR=\(near): max pixel error = \(maxError) (limit: \(near))")
assert(maxError <= near, "Error exceeds NEAR bound")
}
try roundTripNearLossless()Complete workflow for medical imaging with 12-bit depth:
import JPEGLS
func processMedicalImage() throws {
// Medical images often use 12-bit or 16-bit depth
let width = 2048
let height = 2048
let bitsPerSample = 12
let maxValue = (1 << bitsPerSample) - 1 // Calculated maximum value (e.g., 4095 for 12-bit)
// Load medical image data (example: CT scan)
let pixels = loadMedicalImageData(width: width, height: height)
// Create image data
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: bitsPerSample
)
// Use lossless compression for diagnostic images
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print("Medical image encoded:")
print(" Dimensions: \(width)×\(height)")
print(" Bit depth: \(bitsPerSample)-bit (\(maxValue + 1) grey levels)")
print(" Compression: lossless")
// Calculate compression statistics
let uncompressedSize = width * height * 2 // 2 bytes per pixel for 12-bit
print(" Uncompressed size: \(uncompressedSize) bytes")
print(" Compressed size: \(jpegLSData.count) bytes")
print(" Compression ratio: \(String(format: "%.2f", Double(uncompressedSize) / Double(jpegLSData.count)))×")
}
func loadMedicalImageData(width: Int, height: Int) -> [[Int]] {
// Simulate loading a medical image with tissue and bone
// NOTE: This is simplified example code. Production code should use more efficient
// algorithms or pre-computed lookup tables for large images.
var pixels: [[Int]] = []
let centerX = width / 2
let centerY = height / 2
for y in 0..<height {
var row: [Int] = []
for x in 0..<width {
// Simulate Hounsfield units mapped to 12-bit range
// Air: 0, Soft tissue: 2048, Bone: 3500
let dx = x - centerX
let dy = y - centerY
let distanceSquared = dx * dx + dy * dy
let threshold1 = (width / 4) * (width / 4)
let threshold2 = (width / 3) * (width / 3)
let value: Int
if distanceSquared < threshold1 {
value = 3500 // Bone
} else if distanceSquared < threshold2 {
value = 2048 // Soft tissue
} else {
value = 100 // Air
}
row.append(value)
}
pixels.append(row)
}
return pixels
}
try processMedicalImage()Process multiple images efficiently:
import JPEGLS
import Foundation
struct ImageFile {
let path: String
let width: Int
let height: Int
let bitsPerSample: Int
}
func batchEncodeImages(images: [ImageFile]) throws {
print("Processing \(images.count) images...")
var successCount = 0
var failureCount = 0
for (index, imageFile) in images.enumerated() {
do {
print("\n[\(index + 1)/\(images.count)] Processing: \(imageFile.path)")
// Load image data
let pixels = try loadRawImage(path: imageFile.path,
width: imageFile.width,
height: imageFile.height)
// Create image data
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: imageFile.bitsPerSample
)
// Encode using the high-level encoder
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print(" ✓ Success: \(jpegLSData.count) bytes")
successCount += 1
} catch {
print(" ✗ Failed: \(error)")
failureCount += 1
}
}
print("\n--- Batch Summary ---")
print("Total: \(images.count)")
print("Success: \(successCount)")
print("Failed: \(failureCount)")
}
func loadRawImage(path: String, width: Int, height: Int) throws -> [[Int]] {
// Load raw image data from file
let data = try Data(contentsOf: URL(fileURLWithPath: path))
var pixels: [[Int]] = []
var offset = 0
for _ in 0..<height {
var row: [Int] = []
for _ in 0..<width {
if offset < data.count {
let value = Int(data[offset])
row.append(value)
offset += 1
}
}
pixels.append(row)
}
return pixels
}
// Example usage
let images = [
ImageFile(path: "image1.raw", width: 512, height: 512, bitsPerSample: 8),
ImageFile(path: "image2.raw", width: 1024, height: 1024, bitsPerSample: 8),
ImageFile(path: "image3.raw", width: 2048, height: 2048, bitsPerSample: 12)
]
try batchEncodeImages(images: images)Process large images efficiently using restart-interval parallelism (ITU-T.87 DRI/RSTm). There is no tiling API — the codec works on one flat pixel plane per scan; restart intervals are the parallelism and error-resilience mechanism. Supported for lossless (NEAR = 0), non-interleaved scans:
import JPEGLS
func processLargeImageWithRestartIntervals() throws {
let imageWidth = 8192
let imageHeight = 8192
print("Processing large image: \(imageWidth)x\(imageHeight)")
// Load the full frame (test pattern here; read from file in practice)
var pixels: [[Int]] = []
for row in 0..<imageHeight {
var pixelRow: [Int] = []
for col in 0..<imageWidth {
pixelRow.append((row + col) % 256)
}
pixels.append(pixelRow)
}
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
// Large frames: parallelise a single image across cores with restart markers
let config = try JPEGLSEncoder.Configuration(restartInterval: 256)
let jpegLSData = try JPEGLSEncoder().encode(imageData, configuration: config)
// Decoding splits at the RST markers automatically and decodes intervals concurrently.
print("✓ Encoded: \(jpegLSData.count) bytes")
}
try processLargeImageWithRestartIntervals()Use custom preset parameters for specialised compression:
import JPEGLS
func encodeWithCustomPresets() throws {
let width = 512
let height = 512
let pixels = createTestImage(width: width, height: height)
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
// Create custom preset parameters
// Adjust thresholds for different image characteristics
let customPresets = try JPEGLSPresetParameters(
maxValue: 255, // Maximum sample value
threshold1: 5, // Threshold 1 (default: 3)
threshold2: 10, // Threshold 2 (default: 7)
threshold3: 25, // Threshold 3 (default: 21)
reset: 64 // Context reset (default: 64)
)
// Encode using the high-level encoder with custom preset parameters
let encoder = JPEGLSEncoder()
let config = try JPEGLSEncoder.Configuration(
near: 0,
interleaveMode: .none,
presetParameters: customPresets
)
let jpegLSData = try encoder.encode(imageData, configuration: config)
print("Encoded with custom preset parameters:")
print(" Threshold1=\(customPresets.threshold1), Threshold2=\(customPresets.threshold2), Threshold3=\(customPresets.threshold3)")
print(" RESET=\(customPresets.reset)")
print(" Encoded \(jpegLSData.count) bytes")
}
try encodeWithCustomPresets()Compare different interleaving modes for RGB images:
import JPEGLS
func compareInterleavingModes() throws {
let width = 512
let height = 512
// Create RGB test image
let (red, green, blue) = createRGBTestImage(width: width, height: height)
let imageData = try MultiComponentImageData.rgb(
redPixels: red,
greenPixels: green,
bluePixels: blue,
bitsPerSample: 8
)
// Test 1: No interleaving (separate component scans)
print("1. No Interleaving (separate scans):")
try testInterleaving(imageData: imageData, mode: .none)
// Test 2: Line-interleaved
print("\n2. Line-Interleaved:")
try testInterleaving(imageData: imageData, mode: .line)
// Test 3: Sample-interleaved (typical for RGB)
print("\n3. Sample-Interleaved (recommended for RGB):")
try testInterleaving(imageData: imageData, mode: .sample)
}
func testInterleaving(imageData: MultiComponentImageData,
mode: JPEGLSInterleaveMode) throws {
// Encode with specified interleaving mode
let encoder = JPEGLSEncoder()
let config = try JPEGLSEncoder.Configuration(
near: 0,
interleaveMode: mode
)
let jpegLSData = try encoder.encode(imageData, configuration: config)
print(" Mode: \(mode)")
print(" Output size: \(jpegLSData.count) bytes")
}
func createRGBTestImage(width: Int, height: Int) -> ([[Int]], [[Int]], [[Int]]) {
var red: [[Int]] = []
var green: [[Int]] = []
var blue: [[Int]] = []
for y in 0..<height {
var redRow: [Int] = []
var greenRow: [Int] = []
var blueRow: [Int] = []
for x in 0..<width {
// Create color gradients
redRow.append((x * 255) / width)
greenRow.append((y * 255) / height)
blueRow.append(((x + y) * 255) / (width + height))
}
red.append(redRow)
green.append(greenRow)
blue.append(blueRow)
}
return (red, green, blue)
}
try compareInterleavingModes()Colour space transformations decorrelate RGB components and can improve compression ratios for natural images:
import JPEGLS
func encodeWithColourTransform() throws {
let width = 256
let height = 256
// Build a simple RGB gradient test image inline
let red: [[Int]] = (0..<height).map { _ in (0..<width).map { x in (x * 255) / width } }
let green: [[Int]] = (0..<height).map { y in (0..<width).map { _ in (y * 255) / height } }
let blue: [[Int]] = (0..<height).map { y in (0..<width).map { x in ((x + y) * 255) / (width + height) } }
let imageData = try MultiComponentImageData.rgb(
redPixels: red, greenPixels: green, bluePixels: blue, bitsPerSample: 8
)
let encoder = JPEGLSEncoder()
let decoder = JPEGLSDecoder()
for transform in [JPEGLSColorTransformation.none, .hp1, .hp2, .hp3] {
let config = try JPEGLSEncoder.Configuration(
near: 0,
interleaveMode: .sample,
colorTransformation: transform
)
let encoded = try encoder.encode(imageData, configuration: config)
let decoded = try decoder.decode(encoded)
// Verify round-trip — decoded values must match original exactly for lossless
let redDecoded = decoded.components[0].pixels
let greenDecoded = decoded.components[1].pixels
let blueDecoded = decoded.components[2].pixels
var maxErr = 0
for y in 0..<height {
for x in 0..<width {
maxErr = max(maxErr, abs(redDecoded[y][x] - red[y][x]))
maxErr = max(maxErr, abs(greenDecoded[y][x] - green[y][x]))
maxErr = max(maxErr, abs(blueDecoded[y][x] - blue[y][x]))
}
}
print("\(transform): encoded=\(encoded.count) bytes, maxError=\(maxErr)")
}
}
try encodeWithColourTransform()Mapping tables (LSE type 2) allow pixel indices to be looked up in a palette, enabling efficient encoding of palettised images:
import JPEGLS
func exploreMappingTables() throws {
// Build a 4-level greyscale palette: index 0→0, 1→85, 2→170, 3→255
let palette = try JPEGLSMappingTable(id: 1, entryWidth: 1, entries: [0, 85, 170, 255])
// Map raw decoded indices to palette values
print(palette.map(0)) // 0
print(palette.map(1)) // 85
print(palette.map(2)) // 170
print(palette.map(3)) // 255
print(palette.map(99)) // 99 — out-of-range index returned unchanged
print("Palette: \(palette)") // JPEGLSMappingTable(id=1, entryWidth=1, entries=4)
}
try exploreMappingTables()Buffer pooling, cache-friendly data layout, and platform-specific SIMD acceleration are handled internally by the codec — there is no public API for them and no setup is required. For parallelising large frames across cores, use restart intervals:
import JPEGLS
// Large frames: parallelise a single image across cores with restart markers
let config = try JPEGLSEncoder.Configuration(restartInterval: 256)
let encoded = try JPEGLSEncoder().encode(imageData, configuration: config)
// Decoding splits at the RST markers automatically and decodes intervals concurrently.See "Large Image Processing with Restart Intervals" above and PERFORMANCE_TUNING.md for benchmarking guidance.
Process large images with minimal memory footprint:
import JPEGLS
func streamLargeImage() throws {
let totalWidth = 16384
let totalHeight = 16384
let chunkHeight = 512 // Process 512 rows at a time
print("Streaming large image: \(totalWidth)x\(totalHeight)")
print("Chunk size: \(totalWidth)x\(chunkHeight)")
var totalPixelsProcessed = 0
// Process image in horizontal strips
for chunkY in stride(from: 0, to: totalHeight, by: chunkHeight) {
let currentChunkHeight = min(chunkHeight, totalHeight - chunkY)
print("\nProcessing chunk at y=\(chunkY), height=\(currentChunkHeight)")
// Load only one chunk into memory
let chunkPixels = loadImageChunk(
width: totalWidth,
yStart: chunkY,
height: currentChunkHeight
)
// Process the chunk
let imageData = try MultiComponentImageData.grayscale(
pixels: chunkPixels,
bitsPerSample: 8
)
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
totalPixelsProcessed += totalWidth * currentChunkHeight
print(" ✓ Processed \(jpegLSData.count) bytes")
// The chunk data can now be discarded (garbage collected)
// Only one chunk is in memory at a time
}
print("\n✓ Complete: Processed \(totalPixelsProcessed) pixels")
print(" Peak memory: ~\(totalWidth * chunkHeight * 2) bytes per chunk")
}
func loadImageChunk(width: Int, yStart: Int, height: Int) -> [[Int]] {
var pixels: [[Int]] = []
for y in yStart..<(yStart + height) {
var row: [Int] = []
for x in 0..<width {
// Generate test pattern
let value = (x + y) % 256
row.append(value)
}
pixels.append(row)
}
return pixels
}
try streamLargeImage()Handle errors gracefully in production code:
import JPEGLS
import Foundation
enum ProcessingError: Error {
case fileNotFound(String)
case invalidFormat(String)
case encodingFailed(String)
}
func processImageWithErrorHandling(inputPath: String, outputPath: String) {
do {
print("Processing: \(inputPath)")
// Validate input file exists
guard FileManager.default.fileExists(atPath: inputPath) else {
throw ProcessingError.fileNotFound(inputPath)
}
// Load image data
print(" Loading image data...")
let data = try Data(contentsOf: URL(fileURLWithPath: inputPath))
// Parse image dimensions from filename (example: image_512x512.raw)
let filename = URL(fileURLWithPath: inputPath).lastPathComponent
guard let dimensions = parseDimensions(from: filename) else {
throw ProcessingError.invalidFormat("Could not parse dimensions from filename")
}
// Convert raw data to pixel array
print(" Converting to pixel array...")
let pixels = try convertDataToPixels(
data: data,
width: dimensions.width,
height: dimensions.height
)
// Create image data structure
print(" Creating image data structure...")
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: 8
)
// Set up encoder and encode
print(" Encoding...")
let encoder = JPEGLSEncoder()
let jpegLSData = try encoder.encode(imageData)
print(" ✓ Success!")
print(" Encoded \(jpegLSData.count) bytes")
// Save result to output file
try jpegLSData.write(to: URL(fileURLWithPath: outputPath))
print(" Output saved to: \(outputPath)")
} catch let error as JPEGLSError {
print(" ✗ JPEG-LS Error: \(error)")
handleJPEGLSError(error)
} catch let error as ProcessingError {
print(" ✗ Processing Error: \(error)")
} catch {
print(" ✗ Unexpected Error: \(error)")
}
}
func handleJPEGLSError(_ error: JPEGLSError) {
switch error {
case .invalidDimensions(let width, let height):
print(" Resolution \(width)x\(height) is not supported")
print(" Valid range: 1-65535 for both dimensions")
case .invalidBitsPerSample(let bits):
print(" Bit depth \(bits) is not supported")
print(" Valid range: 2-16 bits per sample")
case .invalidComponentCount(let count):
print(" Component count \(count) is not supported")
print(" Valid values: 1 (grayscale) or 3 (RGB)")
case .invalidNearParameter(let near):
print(" NEAR value \(near) is not valid")
print(" Valid range: 0-255")
default:
print(" Error details: \(error)")
}
}
func parseDimensions(from filename: String) -> (width: Int, height: Int)? {
// Example: "image_512x512.raw" -> (512, 512)
let pattern = #"(\d+)x(\d+)"#
guard let regex = try? NSRegularExpression(pattern: pattern),
let match = regex.firstMatch(in: filename, range: NSRange(filename.startIndex..., in: filename)),
match.numberOfRanges == 3,
let widthRange = Range(match.range(at: 1), in: filename),
let heightRange = Range(match.range(at: 2), in: filename),
let width = Int(filename[widthRange]),
let height = Int(filename[heightRange]) else {
return nil
}
return (width, height)
}
func convertDataToPixels(data: Data, width: Int, height: Int) throws -> [[Int]] {
guard data.count >= width * height else {
throw ProcessingError.invalidFormat("Data size mismatch")
}
var pixels: [[Int]] = []
var offset = 0
for _ in 0..<height {
var row: [Int] = []
for _ in 0..<width {
row.append(Int(data[offset]))
offset += 1
}
pixels.append(row)
}
return pixels
}
// Example usage
processImageWithErrorHandling(
inputPath: "test_image_512x512.raw",
outputPath: "output.jls"
)Validate parameters before processing:
import JPEGLS
struct ImageValidation {
static func validateDimensions(width: Int, height: Int) -> Result<Void, String> {
if width < 1 || width > 65535 {
return .failure("Width must be 1-65535, got \(width)")
}
if height < 1 || height > 65535 {
return .failure("Height must be 1-65535, got \(height)")
}
return .success(())
}
static func validateBitsPerSample(_ bits: Int) -> Result<Void, String> {
if bits < 2 || bits > 16 {
return .failure("Bits per sample must be 2-16, got \(bits)")
}
return .success(())
}
static func validateNearValue(_ near: Int) -> Result<Void, String> {
if near < 0 || near > 255 {
return .failure("NEAR must be 0-255, got \(near)")
}
return .success(())
}
static func validatePixelData(pixels: [[Int]], width: Int, height: Int) -> Result<Void, String> {
if pixels.count != height {
return .failure("Expected \(height) rows, got \(pixels.count)")
}
for (index, row) in pixels.enumerated() {
if row.count != width {
return .failure("Row \(index): Expected \(width) pixels, got \(row.count)")
}
}
return .success(())
}
}
func safeImageEncoding(pixels: [[Int]], width: Int, height: Int,
bitsPerSample: Int, near: Int) {
print("Validating image parameters...")
// Validate dimensions
switch ImageValidation.validateDimensions(width: width, height: height) {
case .success:
print(" ✓ Dimensions valid: \(width)x\(height)")
case .failure(let error):
print(" ✗ \(error)")
return
}
// Validate bits per sample
switch ImageValidation.validateBitsPerSample(bitsPerSample) {
case .success:
print(" ✓ Bits per sample valid: \(bitsPerSample)")
case .failure(let error):
print(" ✗ \(error)")
return
}
// Validate NEAR value
switch ImageValidation.validateNearValue(near) {
case .success:
print(" ✓ NEAR value valid: \(near)")
case .failure(let error):
print(" ✗ \(error)")
return
}
// Validate pixel data
switch ImageValidation.validatePixelData(pixels: pixels, width: width, height: height) {
case .success:
print(" ✓ Pixel data valid")
case .failure(let error):
print(" ✗ \(error)")
return
}
// All validations passed, proceed with encoding
do {
print("\nProceeding with encoding...")
let imageData = try MultiComponentImageData.grayscale(
pixels: pixels,
bitsPerSample: bitsPerSample
)
let encoder = JPEGLSEncoder()
let config = try JPEGLSEncoder.Configuration(near: near)
let jpegLSData = try encoder.encode(imageData, configuration: config)
print("✓ Encoding successful!")
print(" Encoded \(jpegLSData.count) bytes")
} catch {
print("✗ Encoding failed: \(error)")
}
}
// Test with valid data
let validPixels = createTestImage(width: 256, height: 256)
safeImageEncoding(
pixels: validPixels,
width: 256,
height: 256,
bitsPerSample: 8,
near: 0
)
// Test with invalid dimensions
print("\n--- Testing invalid dimensions ---")
safeImageEncoding(
pixels: validPixels,
width: 70000, // Invalid: > 65535
height: 256,
bitsPerSample: 8,
near: 0
)Use the CLI tool to inspect JPEG-LS files:
# Get basic information about a JPEG-LS file
jpegls info medical_scan.jls
# Get detailed information in JSON format
jpegls info medical_scan.jls --json | jq .
# Quick one-line summary
jpegls info medical_scan.jls --quiet
# Check multiple files
for file in images/*.jls; do
echo "File: $file"
jpegls info "$file" --quiet
echo ""
doneVerify multiple files in parallel:
# Verify all JPEG-LS files in a directory
jpegls batch verify "images/*.jls" --verbose
# Verify with custom parallelism (useful on systems with many CPU cores)
jpegls batch verify "images/*.jls" --parallelism 8
# Stop on first error (fail-fast mode)
jpegls batch verify "images/*.jls" --fail-fast
# Quiet mode for scripting (exit code indicates success/failure)
if jpegls batch verify "images/*.jls" --quiet; then
echo "All files verified successfully"
else
echo "Verification failed"
exit 1
fiIntegrate JLSwift CLI into scripts:
#!/bin/bash
# automated_processing.sh - Process medical images
INPUT_DIR="./dicom_exports"
OUTPUT_DIR="./compressed"
LOG_FILE="processing.log"
mkdir -p "$OUTPUT_DIR"
echo "Starting batch processing..." | tee "$LOG_FILE"
echo "Input directory: $INPUT_DIR" | tee -a "$LOG_FILE"
echo "Output directory: $OUTPUT_DIR" | tee -a "$LOG_FILE"
echo "" | tee -a "$LOG_FILE"
# Count input files
file_count=$(ls -1 "$INPUT_DIR"/*.raw 2>/dev/null | wc -l)
echo "Found $file_count files to process" | tee -a "$LOG_FILE"
# Process each file
for raw_file in "$INPUT_DIR"/*.raw; do
basename=$(basename "$raw_file" .raw)
output_file="$OUTPUT_DIR/${basename}.jls"
echo "Processing: $basename" | tee -a "$LOG_FILE"
# Encode raw image to JPEG-LS
jpegls encode "$raw_file" "$output_file" \
--width 512 --height 512 \
--bits-per-sample 12 \
--quiet
if [ -f "$output_file" ]; then
jpegls info "$output_file" --quiet >> "$LOG_FILE"
fi
done
echo "" | tee -a "$LOG_FILE"
echo "Processing complete!" | tee -a "$LOG_FILE"
# Verify all output files
echo "Verifying output files..." | tee -a "$LOG_FILE"
jpegls batch verify "$OUTPUT_DIR/*.jls" --quiet
if [ $? -eq 0 ]; then
echo "✓ All files verified successfully" | tee -a "$LOG_FILE"
else
echo "✗ Verification failed" | tee -a "$LOG_FILE"
exit 1
fiJLSwift is a general-purpose JPEG-LS codec with no DICOM dependencies. It can be used in any context where lossless or near-lossless compression of continuous-tone images is needed — no knowledge of DICOM is required.
Compress any greyscale or colour image using JPEG-LS — suitable for scientific imaging, photography, or any application that needs lossless compression:
import JPEGLS
/// Compress raw pixel data to JPEG-LS bytes.
///
/// - Parameters:
/// - pixels: Row-major pixel values (one element per pixel).
/// - width: Image width in pixels.
/// - height: Image height in pixels.
/// - bitsPerSample: Bit depth (2–16).
/// - Returns: Compressed JPEG-LS byte stream.
func compressImage(pixels: [[Int]], width: Int, height: Int, bitsPerSample: Int) throws -> [UInt8] {
let config = try JPEGLSEncoder.Configuration(
width: width,
height: height,
bitsPerSample: bitsPerSample,
componentCount: 1
)
let encoder = JPEGLSEncoder(configuration: config)
return try encoder.encode(components: [pixels])
}
/// Decompress JPEG-LS bytes back to raw pixels.
func decompressImage(data: [UInt8]) throws -> (pixels: [[Int]], width: Int, height: Int) {
let decoder = JPEGLSDecoder()
let result = try decoder.decode(data)
let frame = result.frameHeader
return (result.components[0], frame.width, frame.height)
}
// Round-trip example
let width = 512, height = 512
var pixels = [[Int]](repeating: [Int](repeating: 0, count: width), count: height)
for y in 0..<height {
for x in 0..<width {
pixels[y][x] = (x ^ y) & 0xFF // synthetic test pattern
}
}
let compressed = try compressImage(pixels: pixels, width: width, height: height, bitsPerSample: 8)
let (restored, w, h) = try decompressImage(data: compressed)
print("Compressed \(width)×\(height) to \(compressed.count) bytes (ratio: \(String(format: "%.2f", Double(width * height) / Double(compressed.count)))×)")Use JPEG-LS as a lossless intermediate format for high-quality web assets before final conversion. This preserves full fidelity for assets that require repeated editing:
import JPEGLS
/// Archive lossless JPEG-LS from a 3-component (RGB) image.
func archiveRGBImage(r: [[Int]], g: [[Int]], b: [[Int]], width: Int, height: Int) throws -> [UInt8] {
let config = try JPEGLSEncoder.Configuration(
width: width,
height: height,
bitsPerSample: 8,
componentCount: 3,
interleaveMode: .sample
)
let encoder = JPEGLSEncoder(configuration: config)
return try encoder.encode(components: [r, g, b])
}
/// Restore RGB planes from a JPEG-LS byte stream.
func restoreRGBImage(data: [UInt8]) throws -> (r: [[Int]], g: [[Int]], b: [[Int]]) {
let decoder = JPEGLSDecoder()
let result = try decoder.decode(data)
return (result.components[0], result.components[1], result.components[2])
}Store scientific or sensor data using near-lossless compression to reduce file size while keeping per-pixel error below a specified bound. This is useful for remote sensing, astronomy, or instrument data where slight numerical tolerance is acceptable:
import JPEGLS
/// Compress 16-bit sensor data with a defined per-pixel error tolerance.
///
/// - Parameters:
/// - data: 2-D array of 16-bit sample values.
/// - width: Image width.
/// - height: Image height.
/// - tolerance: Maximum allowed reconstruction error per pixel (NEAR parameter).
/// - Returns: Compressed byte stream.
func archiveSensorData(data: [[Int]], width: Int, height: Int, tolerance: Int) throws -> [UInt8] {
let config = try JPEGLSEncoder.Configuration(
width: width,
height: height,
bitsPerSample: 16,
componentCount: 1,
near: tolerance
)
let encoder = JPEGLSEncoder(configuration: config)
return try encoder.encode(components: [data])
}
// Example: compress 4 Mpixel 16-bit instrument frame with tolerance ≤ 4 DN
let frameWidth = 2048, frameHeight = 2048
// ... populate sensorFrame: [[Int]] with actual instrument data ...
// let compressed = try archiveSensorData(data: sensorFrame, width: frameWidth,
// height: frameHeight, tolerance: 4)
// print("Archive size: \(compressed.count) bytes")- README.md - Project overview and features
- GETTING_STARTED.md - Quick start guide
- PERFORMANCE_TUNING.md - Performance optimisation
- TROUBLESHOOTING.md - Common issues and solutions
- MILESTONES.md - Development roadmap
Generate full API documentation:
swift package generate-documentationExplore the comprehensive test suite for more examples:
# View test files
ls Tests/JPEGLSTests/
# Run specific test suites
swift test --filter JPEGLSMultiComponentEncoderTests
swift test --filter CharLSConformanceTests
swift test --filter JPEGLSPerformanceBenchmarksSee Copilot Instructions for coding guidelines and contribution requirements.
- GETTING_STARTED.md - Quick start guide with basic examples
- SWIFTUI_EXAMPLES.md - SwiftUI integration guide for iOS/macOS apps
- APPKIT_EXAMPLES.md - AppKit integration guide for macOS applications
- PERFORMANCE_TUNING.md - Performance optimisation and benchmarking
- TROUBLESHOOTING.md - Common issues and solutions
For questions or issues, please visit the GitHub repository.