Search code examples
javamatrixblocktransformation

List of blocks to a whole matrix - java


So I'm having the following problem: I have a method that breaks a big matrix into smaller blocks of the same size. After I do some operations on the blocks, I want to reconstruct the big matrix in the right order, but I'm going wrong at it somehow.

The following code reconstructs correctly a 4x4 matrix that breaks into 2x2, but for any other dimensions, it's not working properly.

   public long[][] blocksToMatrix(List<long[][]> blocks, int blockDimension, int width, int height ){
      long[][] yuvMatrix = new long[height][width];
      int heightPos = 0;
      int widthPos = 0;
      for (int i = 0; i < blocks.size(); i++) {
         long[][] yuvBlock = blocks.get(i);
         int heightPosTemp = heightPos;
         for (int j = 0; j < blockDimension * blockDimension; j++) {
            yuvMatrix[heightPos][widthPos] = yuvBlock[j / blockDimension][j % blockDimension];
            widthPos++;
            if (widthPos >= width){
               widthPos = (i * blockDimension) % width;
               heightPos++;
            }
            if (widthPos == ((i + 1) * blockDimension) % width){
               widthPos = (i * blockDimension) % width;
               heightPos++;
            }
         }
         if (heightPos == height ){
            heightPos = heightPosTemp;
         }
         else {
            heightPos = (i * blockDimension) % height;
         }
         widthPos = ((i + 1) * blockDimension) % width;
      }
      return yuvMatrix;
   }

The method I used to break the matrix:

   public List<long[][]> matrixToBlocks(long[][] yuvMatrix, int blockDimension, int width, int height){
      int blocksSize = width / blockDimension * (height / blockDimension);
      List<long[][]> blocks = new ArrayList<long[][]>();
      for (int i = 0; i < blocksSize; i++) {
         long[][] subBlock = new long[blockDimension][blockDimension];
         int heightPos = (blockDimension * (i / blockDimension)) % height;
         int widthPos = (blockDimension * i) % width;
         if (widthPos + blockDimension > width) {
            widthPos = 0;
         }
         for (int row = 0; row < blockDimension; row++) {
            for (int col = 0; col < blockDimension; col++) {
               subBlock[row][col] = yuvMatrix[heightPos + row][col + widthPos];
            }
         }
         blocks.add(subBlock);
      }
      return blocks;
   }

The way I tested it:

   public static void testareMatBlo(int height, int width, int blockdim){
      long[][] test = new long[height][width];
      int val = 1;
      for (int i = 0; i < height; i++){
         for (int j = 0; j < width; j++){
            test[i][j] = val;
            val++;
         }
      }
      List<long[][]> blocks = matrixToBlocks(test, blockdim, width, height);
      long[][] matrix = blocksToMatrix(blocks, blockdim, width, height);
      if (Arrays.deepEquals(test, matrix)){
         System.out.println("YES");
      }
      else {
         System.out.println("NO");
      }
   }

This works:

   testareMatBlo(4, 4, 2);

But anything else doesn't. Can anyone explain what I did wrong?


Solution

  • I didn't thoroughly read your code for matrixToBlocks(...) but all those calculations like int blocksSize = width / blockDimension * (height / blockDimension); are very likely to introduce hard to spot errors - and you actually don't need them:

    public static List<long[][]> matrixToBlocks(long[][] yuvMatrix, int blockDimension){    
      //Check matrix and block dimension match
      if( yuvMatrix.length == 0 || yuvMatrix.length % blockDimension != 0 
        || yuvMatrix[0].length == 0 || yuvMatrix[0].length % blockDimension != 0 ) {
        throw new IllegalArgumentException("whatever message you like");
      }
    
      List<long[][]> blocks = new ArrayList<long[][]>();
    
      //Iterate over the blocks in row-major order (down first, then right)
      for( int c = 0; c < yuvMatrix.length; c += blockDimension ) {
        for( int r = 0; r < yuvMatrix[c].length; r += blockDimension ) {
          long[][] subBlock = new long[blockDimension][blockDimension];
    
          //Iterate over the block in row-major order
          for(int bc = 0; bc < blockDimension; bc++ ) {
            for(int br = 0; br < blockDimension; br++ ) {
              subBlock[bc][br]=yuvMatrix[c+bc][r+br];
            } 
          }    
    
          blocks.add(subBlock);
        }
      }
    
      return blocks;
    }
    

    That method doesn't look shorter but it is: discounting the preliminary check yours is missing there are only 8 actual lines of code compared to 13 in your code. That's not the point however. What's more important is that the logic is easier since there are only a few calculations involved (like c+bc).

    You might think this is inefficient but it isn't: you're accessing each element only once and thus even though there are 4 nested loops the overall complexity is still O(n) with n being the size of the matrix.

    Constructing the matrix back is equally easy. The major thing you need to take care of is the ordering of the blocks: if you create them in row-major order (blocks below each other are next to each other in the list) you need to recreate the matrix in the same way:

    public static long[][] blocksToMatrix( List<long[][]> blocks, int width, int height ) {
      long[][] yuvMatrix = new long[width][height];
      int c = 0;
      int r = 0;
    
      for( long[][] block : blocks ) {
        int blockWidth = block.length;
        int blockHeight = block[0].length;
    
        for( int bc = 0; bc < block.length; bc++ ) {
          for( int br = 0; br < block[bc].length; br++ ) {
            yuvMatrix[c + bc][r + br] = block[bc][br];
          }
        }
    
        //calculate the next offset into the matrix
        //The blocks where created in row-major order so we need to advance the offset in the same way
        r += blockHeight;
        if( r >= height ) {
          r = 0;
          c += blockWidth;
        }
      }
    
      return yuvMatrix;
    }