#####---------------------------------------------------------------------------
## combine info from one Pinnacle patient directory = DVHLst
mergePinnaclePat <- function(x, planInfo=FALSE, courseAsID=FALSE, ...) {
planInfo <- as.character(planInfo)
## we need these files
infoFiles <- c("DoseInfo.csv", "PatInfo.csv", "PlanInfo.csv", "Data/Info.csv")
reqFiles <- paste0(x, "/", infoFiles)
fRead <- if(!all(file_test(op="-f", reqFiles))) {
warning(paste("Required files not found in directory", basename(x)))
NULL
} else {
Map(function(y) {
read.csv(y, header=TRUE, stringsAsFactors=FALSE, comment.char="")
}, reqFiles)
}
if(!is.null(fRead)) {
names(fRead) <- basename(names(fRead))
doseElem <- grep("PrescriptionDose", names(fRead$DoseInfo.csv), ignore.case=TRUE)
doseUnit <- sub("^[[:alpha:]]+[.](cGy|Gy)$", "\\1", names(fRead$DoseInfo.csv)[doseElem], ignore.case=TRUE)
doseRxIdx <- grep("Dosis.+(cGy|Gy)", names(fRead$DoseInfo.csv), ignore.case=TRUE)
fracDoseIdx <- grep("PrescriptionDose", names(fRead$DoseInfo.csv), ignore.case=TRUE)
info <- list(patID=removeWS(fRead$PatInfo.csv$MedicalRecordNumber),
patName=collWS(trimWS(paste(fRead$PatInfo.csv$FirstName, fRead$PatInfo.csv$LastName))),
plan=collWS(trimWS(fRead$PlanInfo.csv$PlanName)),
fractionDose=fRead$DoseInfo[[fracDoseIdx]],
fractionN=fRead$DoseInfo$NumberOfFractions,
doseRx=fRead$DoseInfo[[doseRxIdx]],
doseUnit=toupper(doseUnit))
## read structures
structs <- split(fRead$Info.csv, f=fRead$Info.csv$RegionOfInterestName)
DVHraw <- Map(readLines, paste0(x, "/Data/", fRead$Info.csv$Filename, ".csv"))
dvhL <- Map(parsePinnacleDVH, DVHraw, structs, list(info))
dvhL <- Filter(Negate(is.null), dvhL)
names(dvhL) <- sapply(dvhL, function(y) y$structure)
if(length(unique(names(dvhL))) < length(dvhL)) {
warning("Some structures have the same name - this can lead to problems")
}
class(dvhL) <- "DVHLst"
attr(dvhL, which="byPat") <- TRUE
dvhL
} else {
NULL
}
return(dvhL)
}
#####---------------------------------------------------------------------------
## parse character vector from Pinnacle DVH file
parsePinnacleDVH <- function(x, structInfo, info) {
## function to extract one information element from a number of lines
## make sure only first : is matched -> not greedy
getElem <- function(pattern, ll, trim=TRUE, iCase=FALSE, collWS=TRUE) {
line <- ll[grep(pattern, ll)]
elem <- sub("^.+?=[[:blank:]]+([[:alnum:][:punct:][:blank:]]+);$", "\\1",
line, ignore.case=iCase, perl=TRUE)
elem <- if(trim) {
trimWS(elem, side="both")
} else {
elem
}
if(collWS) {
collWS(elem)
} else {
elem
}
}
dvhCols <- as.numeric(getElem("NumberOfDimensions", x))
dvhLenAdv <- as.numeric(getElem("NumberOfPoints", x))
## DVH column headers
dvhStart <- grep("Points.+{", x, ignore.case=TRUE, perl=TRUE) + 1
dvhStop <- grep("};", x, ignore.case=TRUE, perl=TRUE) - 1
dvhLen <- dvhStop - dvhStart + 1
if((length(dvhStart) < 1L) || length(dvhStop) < 1L) {
stop("No DVH data found")
}
if(dvhLenAdv != dvhLen) {
warning("Advertised DVH length differs from actual length")
}
## extract DVH as a matrix and store preceding information
## check if dvh is all blank -> no data
if(all(!nzchar(x[dvhStart:dvhStop]))) {
return(NULL)
}
## strip trailing ,
x[dvhStart:dvhStop] <- gsub("(^.+),$", "\\1", x[dvhStart:dvhStop])
dvh <- data.matrix(read.csv(text=x[dvhStart:dvhStop],
header=FALSE, stringsAsFactors=FALSE,
colClasses=rep("numeric", dvhCols),
comment.char="", nrows=dvhLen))
## set column names
## TODO: this should be exported
vars3 <- c("dose", "volume")
colnames(dvh) <- vars3
## TODO: this should be exported
warning("Iso-dose-Rx is assumed to be 100")
info$isoDoseRx <- 100
## structure volume
volumeIdx <- grep("^Volume", names(structInfo), ignore.case=TRUE)
info$structVol <- structInfo[[volumeIdx]]
## add information we don't have yet: relative/absolute volume
if(( "volumeRel" %in% vars3) && !("volume" %in% vars3)) {
dvh <- cbind(dvh, volume=info$structVol*(dvh[ , "volumeRel"]/100))
} else if(("volume" %in% vars3) && !("volumeRel" %in% vars3)) {
dvh <- cbind(dvh, volumeRel=100*(dvh[ , "volume"]/info$structVol))
}
## add information we don't have yet: relative/absolute dose
## considering isoDoseRx
if(( "doseRel" %in% vars3) && !("dose" %in% vars3)) {
dvh <- cbind(dvh, dose=dvh[ , "doseRel"]*info$doseRx / info$isoDoseRx)
# (info$doseRx/(info$isoDoseRx/100))*(dvh$doseRel/100)
} else if(("dose" %in% vars3) && !("doseRel" %in% vars3)) {
dvh <- cbind(dvh, doseRel=dvh[ , "dose"]*info$isoDoseRx / info$doseRx)
# 100*(dvh$dose/(info$doseRx/(info$isoDoseRx/100)))
}
## TODO: DVHdate
volumeElem <- grep("Volume", names(structInfo), ignore.case=TRUE)
volumeUnit <- sub("^[[:alpha:]]+[.](.+)$", "\\1", names(structInfo)[volumeElem], ignore.case=TRUE)
if(toupper(volumeUnit) == "CCM") {
volumeUnit <- "CC"
}
## check if dose is increasing
stopifnot(isIncreasing(dvh))
## differential or cumulative DVH
DVHtype <- dvhType(dvh)
## TODO: identify list elements for structVol, dose*, independent of unit
doseMinIdx <- grep("^DoseMin", names(structInfo), ignore.case=TRUE)
doseMaxIdx <- grep("^DoseMax", names(structInfo), ignore.case=TRUE)
doseAvgIdx <- grep("^DoseMean", names(structInfo), ignore.case=TRUE)
DVH <- list(dvh=dvh,
patName=info$patName,
patID=info$patID,
date=info$date,
DVHtype=DVHtype,
plan=info$plan,
fractionDose=info$fractionDose,
fractionN=info$fractionN,
quadrant=info$quadrant,
structure=structInfo$RegionOfInterestName,
structVol=structInfo[[volumeIdx]],
doseUnit=info$doseUnit,
volumeUnit=toupper(volumeUnit),
doseRx=info$doseRx,
isoDoseRx=info$isoDoseRx,
doseMin=structInfo[[doseMinIdx]],
doseMax=structInfo[[doseMaxIdx]],
doseAvg=structInfo[[doseAvgIdx]])
## convert differential DVH to cumulative
## and add differential DVH separately
if(DVHtype == "differential") {
DVH$dvh <- convertDVH(dvh, toType="cumulative",
toDoseUnit="asis", perDose=FALSE)
DVH$dvhDiff <- dvh
}
## set class
class(DVH) <- "DVHs"
return(DVH)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.