# 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? – José Antonio Díaz Navas Feb 2 '18 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. – Majis Feb 2 '18 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 – José Antonio Díaz Navas Feb 2 '18 at 12:20
• @JoséAntonioDíazNavas May be. But on my machine, it can't even compute for 4x4 blocks. – Majis Feb 2 '18 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. – Majis Feb 2 '18 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? – José Antonio Díaz Navas Feb 2 '18 at 13:08
• I'm using Intel i7 7700 (Windows 10). – Anjan Kumar Feb 2 '18 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 – José Antonio Díaz Navas Feb 2 '18 at 13:48
• @Coolwater How can you access each individual part following your approach? – Majis Feb 2 '18 at 17:41
• @Majis I think it depends on the purpose how that should be done, see edit – Coolwater Feb 2 '18 at 18:19
• @Coolwater Thanks. While calculating parts you've used img twice (probably by mistake). – Majis Feb 2 '18 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. – b3m2a1 Feb 2 '18 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! – Alucard Feb 2 '18 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. – b3m2a1 Feb 2 '18 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 ? – Alucard Feb 2 '18 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. – b3m2a1 Feb 2 '18 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... – José Antonio Díaz Navas Feb 2 '18 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. – b3m2a1 Feb 2 '18 at 20:20
• uhm i tried adding DeveloperToPackedArray@ but the timing doesn't change much ( more or less 1 millisecond) – Alucard Feb 2 '18 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. – b3m2a1 Feb 2 '18 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 – Alucard Feb 2 '18 at 20:56