【问题标题】:How to decode PostgreSQL bytea column hex to int16/uint16 in r?如何在 r 中将 PostgreSQL bytea 列十六进制解码为 int16/uint16?
【发布时间】:2018-06-22 23:32:17
【问题描述】:

我有一些图像数据作为 bytea 存储在 PostgreSQL 数据库表列中。我还有关于数据的元数据用于解释它,相关的是图像尺寸和类别。类包括 int16、uint16。我找不到任何关于在 R 中正确解释有符号/无符号整数的信息。

我正在使用 RPostgreSQL 将数据拉入 R,我想在 R 中查看图像。

MWE:

# fakeDataQuery <- dbGetQuery(conn, 
#     'select byteArray, ImageSize, ImageClass from table where id = 1')

# Example 1 (no negative numbers)
# the actual byte array shown in octal sequences in pgadmin (1.22.2) Query Output is: 
# "\001\000\002\000\003\000\004\000\005\000\006\000\007\000\010\000\011\000"

# but RPostgreSQL returns the hex-encoded version:
byteArray <- "\\x010002000300040005000600070008000900"
ImageSize <- c(3, 3, 1)
ImageClass <- 'int16'

# expected result 
> array(c(1,2,3,4,5,6,7,8,9), dim=c(3,3,1))
#   , , 1
#
#        [,1] [,2] [,3]
#[1,]    1    4    7
#[2,]    2    5    8
#[3,]    3    6    9

# Example 2: (with negtive numbers)
byteArray <- "\\xffff00000100020003000400050006000700080009000a00"
ImageSize <- c(3, 4, 1)
ImageClass <- 'int16'
# expectedResult 
> array(c(-1,0,1,2,3,4,5,6,7,8,9,10), dim=c(3,4,1))
#, , 1
#
#     [,1] [,2] [,3] [,4]
#[1,]   -1    2    5    8
#[2,]    0    3    6    9
#[3,]    1    4    7   10

我尝试过的:

来自 PostgreSQL 的 bytea 数据是一个长字符串,编码为“十六进制”,您可以通过前面的 \\x 来判断它(我相信还有一个额外的 \ 用于转义现有的?): https://www.postgresql.org/docs/9.1/static/datatype-binary.html(参见:第 8.4.1 节“bytea Hex 格式”)

将“hex”解码回原始类型(基于 ImageClass 的“int16”)

根据上述the same url,十六进制编码使用“每字节 2 个十六进制数字”。所以我需要将编码后的 byteArray 拆分成合适长度的子串,见:this link

# remove the \\x hex encoding indicator(s) added by PostgreSQL
byteArray <- gsub("\\x", "", x = byteArray, fixed=T)

l <- 2  # hex digits per byte (substring length)
byteArray <- strsplit(trimws(gsub(pattern = paste0("(.{",l,"})"), 
                                  replacement = "\\1 ", 
                                  x = byteArray)), 
                      " ")[[1]]

# for some reason these appear to be in the opposite order than i expect
# Ex: 1 is stored as '0100' rather than '0001'
# so reverse the digits (int16 specific)
byteArray <- paste0(byteArray[c(F,T)],byteArray[c(T,F)])

# strtoi() converts a vector of hex values given a decimal base
byteArray <- strtoi(byteArray, 16L)

# now make it into an n x m x s array,
# e.g., 512 x 512 x (# slices)
V = array(byteArray, dim = ImageSize)

这个解决方案有两个问题:

  1. 它不适用于有符号类型,因此负整数值将被解释为无符号值(例如,'ffff' 为 -1 (int16) 但 65535 (uint16) 和 strtoi() 将始终返回 65535)。李>
  2. 目前仅针对 int16 进行编码,需要一些额外的代码才能与其他类型(例如 int32、int64)一起使用

任何人都有可以使用签名类型的解决方案吗?

【问题讨论】:

    标签: r postgresql hex decode bytea


    【解决方案1】:

    您可以从this conversion function 开始,替换为更快的strsplit 并在结果上使用readBin

    byteArray <- "\\xffff00000100020003000400050006000700080009000a00"
    
    ## Split a long string into a a vector of character pairs
    Rcpp::cppFunction( code = '
    CharacterVector strsplit2(const std::string& hex) {
      unsigned int length = hex.length()/2;
      CharacterVector res(length);
      for (unsigned int i = 0; i < length; ++i) {
        res(i) = hex.substr(2*i, 2);
      }
      return res;
    }')
    
    ## A function to convert one string to an array of raw
    f <- function(x)  {
      ## Split a long string into a a vector of character pairs
      x <- strsplit2(x)
      ## Remove the first element, "\\x"
      x <- x[-1]
      ## Complete the conversion
      as.raw(as.hexmode(x))
    }
    
    raw <- f(byteArray)
    # int16
    readBin(con = raw,
            what = "integer",
            n = length(raw) / 2,
            size = 2,
            signed = TRUE,
            endian = "little")
    # -1  0  1  2  3  4  5  6  7  8  9 10
    
    # uint16
    readBin(con = raw,
            what = "integer",
            n = length(raw) / 2,
            size = 2,
            signed = FALSE,
            endian = "little")
    # 65535     0     1     2     3     4     5     6     7     8     9    10
    
    # int32
    readBin(con = raw,
            what = "integer",
            n = length(raw) / 4,
            size = 4,
            signed = TRUE,
            endian = "little")
    # 65535 131073 262147 393221 524295 655369
    

    这不适用于 uint32(u)int64,因为 R 在内部使用 int32。但是,R 也可以使用numerics 来存储 2^52 以下的整数。所以我们可以使用这个:

    # uint32
    byteArray <- "\\xffffffff0100020003000400050006000700080009000a00"
    int32 <- readBin(con = f(byteArray),
                     what = "integer",
                     n = length(raw) / 4,
                     size = 4,
                     signed = TRUE,
                     endian = "little")
    
    ifelse(int32 < 0, int32 + 2^32, int32)
    # 4294967295     131073     262147     393221     524295     655369
    

    对于gzip压缩数据:

    # gzip
    byteArray <- "\\x1f8b080000000000000005c1870100200800209a56faffbd41d30dd3b285e37a52f9d033018818000000"
    con <- gzcon(rawConnection(f(byteArray)))
    readBin(con = con,
            what = "integer",
            n = length(raw) / 2,
            size = 2,
            signed = TRUE,
            endian = "little")
    close(con = con)
    

    由于这是一个真正的连接,我们必须确保关闭它。

    【讨论】:

    • @BrianD 有趣的问题。你可以使用readBin(con = gzcon(rawConnection(f(byteArray))), ...)
    • 所以事实证明f() 中的strsplit() 与一个非常大的向量(例如,512x512)中断。它与 CPU 挂钩,永不返回,我必须终止进程或重新启动 R。但是,如果我在我的问题中使用 strsplit() 的版本,它工作得很好。
    • 我正在对压缩和未压缩的 byteArrays 进行一些微基准测试,发现这些连接仍然存在......最终遇到了 R 的 128 个连接限制,并导致错误。因此,我将其拆分为 c &lt;- gzcon(...) 并将 on.exit(close(c)) 添加到执行工作的函数中。这停止了​​错误。
    • @BrianD 谢谢,我已经采纳了你的建议。
    • 显然有更快的字符串拆分版本,请参阅@GSee 答案基准测试:stackoverflow.com/questions/2247045/…
    猜你喜欢
    • 1970-01-01
    • 2013-04-09
    • 2020-06-15
    • 1970-01-01
    • 2013-10-30
    • 2017-10-15
    • 2015-02-14
    • 2021-11-20
    • 2013-09-25
    相关资源
    最近更新 更多