# How fast can I split an image?

Consider an image(img) as

I have defined two functions for splitting and displaying as follows:

splitImage[img_, n_] := Module[{w, h, parts},
{w, h} = ImageDimensions[img];
parts = ImagePartition[img, {w/n, h/n}]
];

showParts[parts_] := Module[{},
ImageAssemble[parts /. i_Image :> ImagePad[i, 1, 1]]
];


Now,

showParts[splitImage[img, 64]] // AbsoluteTiming


gives me the timing as

4.61676

I need to split the image into 64x64 blocks and I want to speed up the process as much as possible.

How can I optimize the speed?

Edit 1 Here is the performance comparison of all the four techniques (executed on my machine):

@CoolWater: 0.0707495

@AnjanKumar: 0.510273

@Majis: 6.95316

@Alucard: 12.5303

Edit 2: Performance comparison updated for splitting

Edit 2:

Now I need you to recommend me which technique I should follow to access each individual part after splitting so that the combined speed will be the fastest.

[I can do the comparison myself but not all the techniques give me a direct access to the individual parts.]

• why not Grid@ImagePartition[img, 64] ? This does the same and it is about 1000x faster in my old Intel I3. Or am I missing something? Commented Feb 2, 2018 at 12:01
• @JoséAntonioDíazNavas I need 64x64 blocks (which in this case will be of the size 4x4) and not blocks of size 64x64. Commented Feb 2, 2018 at 12:17
• I obtain a time 150x faster for a showing blocks of 4x4 for an image of 512x512, using what I proposed above Commented Feb 2, 2018 at 12:20
• @JoséAntonioDíazNavas May be. But on my machine, it can't even compute for 4x4 blocks. Commented Feb 2, 2018 at 12:34
• @JoséAntonioDíazNavas Yes, you are right. It's more than 150 times faster. But I also need a similar display what I can get using showParts. Using Grid the spacing is very large. Commented Feb 2, 2018 at 12:46

Here is a solution that is a magnitude faster than the fastest method so far when it comes to rendering the image:

showParts[img_, n_] := Module[{data, h, w, nrows, ncols, i, j, tmp, new},
data = ImageData[img, "Byte"];
{h, w} = Most@Floor[Dimensions[data]/n];
{nrows, ncols} = Most@Dimensions[data];
{nrows, ncols} = {h Quotient[nrows, h], w Quotient[ncols, w]};
data = Part[data, ;; nrows, ;; ncols];

i = Rest@Most@Range[nrows + 2 nrows/h];
i = Drop[i, {h + 1, UpTo[Infinity], h + 2}];
i = Drop[i, {h + 1, UpTo[Infinity], h + 1}];

j = Rest@Most@Range[ncols + 2 ncols/w];
j = Drop[j, {w + 1, UpTo[Infinity], w + 2}];
j = Drop[j, {w + 1, UpTo[Infinity], w + 1}];

tmp = ConstantArray[DeveloperToPackedArray[{255, 255, 255}], {nrows + 2 nrows/h, ncols}];
tmp[[i]] = data;

new = ConstantArray[DeveloperToPackedArray[{255, 255, 255}], {nrows + 2 nrows/h, ncols + 2 ncols/w}];
new[[All, j]] = tmp;

Image[new, "Byte"]
]

showParts[img, 64] // AbsoluteTiming


The image data is never unpacked in this code.

To get the partitions, we can then apply OP's splitImage function, with good performance:

splitImage[showParts[img, 64], 64]; // AbsoluteTiming


{0.019043, Null}

(This will give us partitions that have been padded with a white border. It is unclear to me if this was desired on its own, or if those were only produced to be able to construct the image. In either case, here it is, if desired.)

One way would be to first extract the ImageData, Partition it, ArrayPad with {1, 1, 1}, convert it to Image, and finally use ImageAssemble.

arrPadImage = (ArrayPad[#, {{1}, {1}}, Hold[{1, 1, 1}]] // ReleaseHold // Image) &;
Partition[ImageData[img], Floor@(ImageDimensions[img]/n)] //


For the given image, this method takes about 0.34 s compared to yours which takes about 3.63 s.

imagePartitionPad[imsq, 64]


ImageData[imagePartitionPad[imsq, 64]] == ImageData[showParts[splitImage[imsq, 64]]]


True

• Please, what processor do you use? Commented Feb 2, 2018 at 13:08
• I'm using Intel i7 7700 (Windows 10). Commented Feb 2, 2018 at 13:09

This is almost the other answer, just slightly more vectorized and using integers for the data array which seems to be faster:

split[img_, n_] := Image[ArrayFlatten[ArrayPad[Partition[
ImageData[img, "Byte"], Reverse[Floor[ImageDimensions[img]/n]]],
{{0, 0}, {0, 0}, {1, 1}, {1, 1}}, {{{{{255, 255, 255}}}}}]], "Byte"]

split[ImageAssemble[{{img, img}}], 64]


Edit: To extract parts:

Clear[split];
split[im_, n_] := Partition[ImageData[im, "Byte"], Reverse[Floor[ImageDimensions[im]/n]]]

show[parts_] := Image[ArrayFlatten[ArrayPad[parts, {{0, 0}, {0, 0}, {1, 1}, {1, 1}},
{{{{{255, 255, 255}}}}}]], "Byte"]

parts = split[ImageAssemble[{{img, img}}], 64];
subImages = Map[Image[#, "Byte"] &, parts, {2}];

show[parts]

• A bit faster that the previous Commented Feb 2, 2018 at 13:48
• @Coolwater How can you access each individual part following your approach? Commented Feb 2, 2018 at 17:41
• @Majis I think it depends on the purpose how that should be done, see edit Commented Feb 2, 2018 at 18:19
• @Coolwater Thanks. While calculating parts you've used img twice (probably by mistake). Commented Feb 2, 2018 at 18:31
• If you pack all your results you can go from ~.080s to ~.055s (i.e. toss DeveloperToPackedArray@ in front of Partition, ArrayPad, and ArrayFlatten. Commented Feb 2, 2018 at 19:20

Here's a super naive (but nonetheless very fast) method for generating the padded image, not for splitting (since that problem is almost trivially handled via Partition) that preserves the ImageColorSpace and leverages the power of PackedArray by not padding with 1 but rather with 1.:

splitty[img_, n_, padColor : {__?NumericQ} | _?NumericQ : 1] :=

Module[
{
data = ImageData@img,
dd,
ins1,
ins2,
},
dd = Dimensions[data];
N@
_?NumericQ,
_,
];
ins1 =
Insert[data,
Flatten[
Table[
{i, j},
{i, 1, dd[[1]]},
{j, 1, dd[[2]], Floor[dd[[1]]/n]}
],
1
]
];
ins2 =
Insert[
ins1,
List /@ Range[1, Length@ins1,  Floor[dd[[2]]/n]]
];
Image[ins2,
ColorSpace -> ImageColorSpace@img
]
];


Note that it performs surprisingly well:

splitty[img, 64] // RepeatedTiming // First

0.014

splitty[img, 64]


And you can insert an arbitrary color:

splitty[img, 100, .5]


splitty[img, 100, {.6, .6, 0}]


• so you worked directly on the original data,adding a constant array in the right position. very clever and fast! Commented Feb 2, 2018 at 21:29
• The real trick is actually that N call (basically using 1. instead of 1). This allows Mathematica to keep the data as a PackedArray as it maintains the type of the data (Real vs. Integer) on which vectorized operations can be applied incredibly fast. Commented Feb 2, 2018 at 21:32
• do i get any advantage if i replace 1 with 1. inside hold or does it convert tthe machine precision number back to 1 ? Commented Feb 2, 2018 at 21:34
• Not a major one, but like .04 seconds on a few tests I just ran. The problem is the Hold. Hold can't be interpreted as a numeric type and so Mathematica unpacks the array. Commented Feb 2, 2018 at 21:37
table = Flatten@ImagePartition[image, ImageDimensions[image]/64]
ImageCollage[table, ImagePadding -> 1, Background -> White,
Method -> "Grid"]


gives:

Update: as Josè noted, my answer, though formally correct, is slower than the original and should not be considered.

New Solution:

data= ImageData[image];
zn = Image@
ReleaseHold@
ArrayFlatten@
BlockMap[ArrayPad[#, {{0, 1}, {1, 0}}, Hold[{1., 1., 1.}]] &, data,
Floor[Dimensions[data]/64][[1 ;; 2]]] // RepeatedTiming


while the first answer took almost 17 seconds to show the result, the new one, on the same laptop, ends in just

0.076s

• Please check, it is slower... Commented Feb 2, 2018 at 13:46
• As I noted to Coolwater, you can (almost certainly) get a speed boost by packing your arrays. I haven't tested it in your case, but since your and Coolwater's solutions are essentially the same my assumption is the speed boost will still apply. Commented Feb 2, 2018 at 20:20
• uhm i tried adding DeveloperToPackedArray@ but the timing doesn't change much ( more or less 1 millisecond) Commented Feb 2, 2018 at 20:27
• @Alucard where'd you add it? The BlockMap might change the efficiency. Also your solution isn't entirely robust. First off your Imagedata isn't correct and also when it works on an image with an alpha channel it fails. Commented Feb 2, 2018 at 20:43
• @b3m2a1 i added it near ArrayPad . i can fix easily ImageData but , in the case of images with an Alphachannel ,replacing Hold[{1,1,1}] with a 4-dimensional vector change the original colors Commented Feb 2, 2018 at 20:56