Skip to content

Commit

Permalink
feat: affine
Browse files Browse the repository at this point in the history
feat: `affine2d` class

feat: `affine2d` initialize

- converts inputs to anchor slot to extent numeric vectors (xmin, xmax, ymin, ymax)
- regenerates rotate, shear, scale, translate values for the affine after every call

feat: helpful internals for objects with `ext()`

- `.get_centroid_xy()` internal for getting numeric centroid xy values of any object that responds to `ext()`
- `.bound_poly()` internal for generating a dummy polygon from the extent of any object that responds to `ext()`

feat: define `affine()` generic

feat: `shear()`

feat: `affine()`

- change `decomp_affine()` to an internal that runs when `affine()` is called on a `matrix`
- `.aff_shift_2d()`, `.aff_shift_2d<-()`, `.aff_linear_2d`, `.aff_linear_2d()` internals for accessing and manipulating affine matrices

feat: extract methods for `affine2d`

feat: `plot()` for `affine2d`

- plots the affine transform described by an `affine2d` class. blue is start, red is end

feat: `rescale()` for `affine2d`

feat: `show()` for `affine2d`

feat: `spatShift()` for `affine2d`

chore: update news

chore: document
  • Loading branch information
jiajic committed Jun 18, 2024
1 parent a0bae74 commit b316ae1
Show file tree
Hide file tree
Showing 25 changed files with 505 additions and 201 deletions.
5 changes: 2 additions & 3 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

S3method(.DollarNames,affine2d)
S3method(.DollarNames,dimObj)
S3method(.DollarNames,metaData)
S3method(.DollarNames,spatEnrObj)
Expand All @@ -8,8 +9,6 @@ S3method(.DollarNames,terraVectData)
S3method(as.data.table,SpatVector)
S3method(as.data.table,giottoPoints)
S3method(as.data.table,giottoPolygon)
S3method(print,affine_decomp)
S3method(solve,affine_decomp)
S3method(t,spatLocsObj)
S3method(t,spatialNetworkObj)
export(addCellMetadata)
Expand Down Expand Up @@ -102,7 +101,6 @@ export(create_spat_grid_obj)
export(create_spat_locs_obj)
export(create_spat_net_obj)
export(cropGiottoLargeImage)
export(decomp_affine)
export(distGiottoImage)
export(edge_distances)
export(estimateImageBg)
Expand Down Expand Up @@ -361,6 +359,7 @@ exportMethods(reconnect)
exportMethods(rescale)
exportMethods(rownames)
exportMethods(setGiotto)
exportMethods(shear)
exportMethods(spatIDs)
exportMethods(spatShift)
exportMethods(spatUnit)
Expand Down
10 changes: 7 additions & 3 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@

## new
- `affine()` for `giottoPolygon`, `giottoPoints`, `spatLocsObj`
- `decomp_affine()` for decomposing affine matrix to simple transforms
- `shear()` for `spatLocsObj`
- `solve()` method for `affine_decomp`
- `shear()` for `giottoPoints`, `giottoPolygon`, `spatLocsObj`, `affine2d`
- `affine2d` class for accumulating linear transforms to be used with `affine()`
- `spin()`, `rescale`, `spatShift()` methods for `affine2d`
- `initialize()`, `[`, `$`, `show()`, `plot()` methods for `affine2d`
- `.get_centroid_xy()` internal for getting numeric centroid xy values of any object that responds to `ext()`
- `.bound_poly()` internal for generating a dummy polygon from the extent of any object that responds to `ext()`
- `.aff_shift_2d()`, `.aff_shift_2d<-()`, `.aff_linear_2d`, `.aff_linear_2d()` internals for accessing and manipulating affine matrices


# GiottoClass 0.3.1 (2024/05/21)
Expand Down
28 changes: 28 additions & 0 deletions R/classes.R
Original file line number Diff line number Diff line change
Expand Up @@ -1630,6 +1630,34 @@ giottoLargeImage <- setClass(
)
)




setClass(
Class = "affine2d",
slots = list(
anchor = "ANY",
affine = "matrix",
order = "character",
rotate = "numeric",
shear = "numeric",
scale = "numeric",
translate = "numeric"
),
prototype = list(
anchor = c(-180, 180, -90, 90),
affine = diag(rep(1, 2L)),
order = c("rotate", "shear", "scale", "translate"),
rotate = 0,
shear = c(0, 0),
scale = c(1, 1),
translate = c(0, 0)
)
)




# function for updating image objects if structure definitions have changed
.update_giotto_image <- function(x) {
if (inherits(x, "giottoLargeImage")) {
Expand Down
2 changes: 1 addition & 1 deletion R/generics.R
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ setGeneric(

# Methods and documentations found in methods-spatShift.R
setGeneric("spatShift", function(x, ...) standardGeneric("spatShift"))
setGeneric("affine", function(x, ...) standardGeneric("affine"))
setGeneric("affine", function(x, y, ...) standardGeneric("affine"))
setGeneric("shear", function(x, ...) standardGeneric("shear"))

# Methods and documentations found in methods-overlaps.R
Expand Down
14 changes: 12 additions & 2 deletions R/giotto_structures.R
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,19 @@
}


# from a spatvector, get the centroid xy values as a numeric vector
.get_centroid_xy <- function(x) {
res <- centroids(x) %>% ext() %>% .ext_to_num_vec()
res[c(1L, 3L)]
}



# create a polygon of the extent.
# useful for plotting and figuring out how the full data will behave spatially
.bound_poly <- function(x) {
res <- ext(x) %>% as.polygons()
res$id <- "bound"
return(res)
}



Expand Down
151 changes: 69 additions & 82 deletions R/methods-affine.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#' @include classes.R

# docs ----------------------------------------------------------- #
#' @title Apply an affine tranform
#' @title Affine transformations
#' @name affine
#' @description Apply an affine transformation matrix to a spatial object.
#' Currently only works for 2D transforms.
Expand Down Expand Up @@ -44,36 +44,55 @@ NULL

#' @rdname affine
#' @export
setMethod("affine", signature("SpatVector"), function(x, m, inv = FALSE, ...) {
.affine_sv(x, m, inv, ...)
setMethod("affine", signature(x = "ANY", y = "missing"), function(x) {
x <- as.matrix(x)
if (ncol(x) <= 3) {
res <- new("affine2d", affine = x)
}
return(res)
})

#' @rdname affine
#' @export
setMethod("affine", signature(x = "ANY", y = "affine2d"), function(x, y, ...) {
a <- get_args_list(...)
a$y <- y@affine
do.call(affine, args = a)
})

#' @rdname affine
#' @export
setMethod("affine", signature(x = "SpatVector", y = "matrix"),
function(x, y, inv = FALSE, ...) {
.affine_sv(x, m = y, inv, ...)
})

#' @rdname affine
#' @export
setMethod(
"affine", signature("giottoPoints"),
function(x, m, inv = FALSE, ...) {
x[] <- .affine_sv(x = x[], m = m, inv = inv, ...)
"affine", signature(x = "giottoPoints", y = "matrix"),
function(x, y, inv = FALSE, ...) {
x[] <- .affine_sv(x = x[], m = y, inv = inv, ...)
return(x)
}
)

#' @rdname affine
#' @export
setMethod(
"affine", signature("giottoPolygon"),
function(x, m, inv = FALSE, ...) {
.do_gpoly(x, what = .affine_sv, args = list(m = m, inv = inv, ...))
"affine", signature(x = "giottoPolygon", y = "matrix"),
function(x, y, inv = FALSE, ...) {
.do_gpoly(x, what = .affine_sv, args = list(m = y, inv = inv, ...))
}
)

#' @rdname affine
#' @export
setMethod(
"affine", signature("spatLocsObj"),
function(x, m, inv = FALSE, ...) {
"affine", signature("spatLocsObj", y = "matrix"),
function(x, y, inv = FALSE, ...) {
x[] <- .affine_dt(
x = x[], m = m, xcol = "sdimx", ycol = "sdimy", inv = inv, ...
x = x[], m = y, xcol = "sdimx", ycol = "sdimy", inv = inv, ...
)
return(x)
}
Expand All @@ -89,7 +108,6 @@ setMethod(
m <- as.matrix(m)
gtype <- terra::geomtype(x)
xdt <- data.table::as.data.table(x, geom = "XY")

xdt <- .affine_dt(
x = xdt, m = m, xcol = "x", ycol = "y", inv = inv, ...
)
Expand Down Expand Up @@ -165,13 +183,13 @@ setMethod(
#' 100, 29, 1
#' ), nrow = 3, byrow = TRUE)
#'
#' # show decomps
#' # create affine objects
#' # values are shown in order of operations
#' decomp_affine(m)
#' decomp_affine(trans_m)
#' decomp_affine(scale_m)
#' s <- decomp_affine(shear_m)
#' a <- decomp_affine(aff_m)
#' affine(m)
#' affine(trans_m)
#' affine(scale_m)
#' s <- affine(shear_m)
#' a <- affine(aff_m)
#' force(a)
#'
#' # perform piecewise transforms with decomp
Expand All @@ -193,9 +211,7 @@ setMethod(
#' plot(affine(sl, aff_m))
#' plot(sl_aff_piecewise)
#'
#' @export
#' @seealso [solve.affine_decomp()]
decomp_affine <- function(x) {
.decomp_affine <- function(x) {
# should be matrix or coercible to matrix
x <- as.matrix(x)

Expand All @@ -219,12 +235,13 @@ decomp_affine <- function(x) {
# apply xy translations
if (ncol(x) == 3) {
res$translate = res$translate + x[seq(2), 3]
} else {
# append translations
x <- cbind(x, rep(0, 2L)) %>%
rbind(c(0, 0, 1))
}

res <- structure(
.Data = c(res, list(matrix = x)), class = "affine_decomp"
)

res$affine <- x
return(res)
}

Expand Down Expand Up @@ -280,68 +297,38 @@ decomp_affine <- function(x) {
)
}



#' @name print.affine_decomp
#' @title affine_decomp print method
#' @param x object to print
#' @param \dots additional params to pass (none implemented)
#' @keywords internal
#' @export
print.affine_decomp <- function(x, ...) {
cat("<affine_decomp>\n")
# reorder in order of piecewise transforms
x <- x[x$order]
print_list(x)
.aff_linear_2d <- function(x) {
if (inherits(x, "affine2d")) x <- x[]
x[][seq(2), seq(2)]
}

#' @name solve.affine_decomp
#' @title Solve for inverted 2D affine decomp
#' @param a `affine_decomp` object
#' @param b not used
#' @param \dots additional params to pass (none implemented)
#' @examples
#' sl <- GiottoData::loadSubObjectMini("spatLocsObj")
#'
#' m <- matrix(c(
#' 2, 0.5, 1000,
#' -0.3, 3, 20,
#' 100, 29, 1
#' ), nrow = 3, byrow = TRUE)
#'
#' a <- decomp_affine(m)
#' i <- solve(decomp) # inverted
#'
#' aff_sl <- affine(sl, aff_m)
#' inv_sl <- aff_sl %>%
#' spatShift(dx = i$translate[["x"]], dy = i$translate[["y"]]) %>%
#' spin(GiottoUtils::degrees(i$rotate), x0 = 0, y0 = 0) %>%
#' shear(fx = i$shear[["x"]], fy = i$shear[["y"]], x0 = 0, y0 = 0) %>%
#' rescale(fx = i$scale[["x"]], fy = i$scale[["y"]], x0 = 0, y0 = 0)
#'
#' plot(aff_sl)
#' plot(sl)
#' plot(inv_sl) # same as original
#'
#' @export
solve.affine_decomp <- function(a, b, ...) {
m <- a$matrix[seq(2), seq(2)]
m <- solve(m)
# translations <- NULL
translation <- -a$translate

inv_m <- rbind(m, c(0,0)) %>%
cbind(c(translation, 1))
`.aff_linear_2d<-` <- function(x, value) {
checkmate::assert_matrix(value, nrows = 2L, ncols = 2L)
if (inherits(x, "affine2d")) {
x[][seq(2), seq(2)] <- value
x <- initialize(x)
}
else x[seq(2), seq(2)] <- value

res <- decomp_affine(inv_m)
res$order <- c("translate", "rotate", "shear", "scale")
class(res) <- "affine_decomp"
return(res)
return(x)
}

.aff_shift_2d <- function(x) {
if (inherits(x, "affine2d")) x <- x[]
x[seq(2), 3]
}



`.aff_shift_2d<-` <- function(x, value) {
checkmate::assert_numeric(value, len = 2L)
if (inherits(x, "affine2d")) {
x[][seq(2), 3] <- value
x <- initialize(x)
} else {
x[seq(2), 3] <- value
}

return(x)
}



Expand Down
46 changes: 46 additions & 0 deletions R/methods-extract.R
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,24 @@ setMethod(
names(x@spatVector)
}

#' @rdname extract-methods
#' @section \code{`$`} methods:
#' Select piecewise transform values from `affine2d`
#' @export
setMethod("$", signature("affine2d"), function(x, name) {
accessible <- c("affine", "order", "rotate", "shear", "scale", "translate")
if (name %in% accessible) {
return(slot(x, name))
} else {
return(NULL)
}
})

#' @export
.DollarNames.affine2d <- function(x, pattern) {
c("affine", "order", "rotate", "shear", "scale", "translate")
}


# [ S4 access generic ####

Expand Down Expand Up @@ -955,3 +973,31 @@ setMethod(
x
}
)

#' @rdname extract-methods
#' @export
setMethod(
"[",
signature(
x = "affine2d", i = "missing", j = "missing", drop = "missing"
),
function(x) {
x@affine
}
)

#' @rdname extract-methods
#' @aliases [<-,affine2d,missing,missing,
#' ANY-method [<-,affine2d,missing,missing-method
#' @docType methods
#' @section \code{`[<-`} methods:
#' Assign to \code{affine} slot in affine2d
#' @export
setMethod(
"[<-",
signature(x = "affine2d", i = "missing", j = "missing", value = "ANY"),
function(x, value) {
x@affine <- value
return(initialize(x))
}
)
Loading

0 comments on commit b316ae1

Please sign in to comment.