What's new
Photoshop Gurus Forum

Welcome to Photoshop Gurus forum. Register a free account today to become a member! It's completely free. Once signed in, you'll enjoy an ad-free experience and be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Mosaic Stitching


IanC

Active Member
Messages
27
Likes
1
I'm wanting to stitch 10 rows x 10 columns of TIF images (each a 4000x4000px map tile). So a 40,000x40,000px TIF output. They're not overlapping, and abut perfectly. The filenames of the input files will define their position in the mosaic.

I could do it manually, but that would take some time, and needs repeating 55 times. So not viable.

Is this feasible in Photoshop, perhaps with a script? I used this program a long time ago that once worked very well, but it won't handle TIF's in or out! And didn't work correctly even after I converted the inputs to BMP.

Or is there a standalone utility that will better do this?

Thanks in advance!
 

IanC

Active Member
Messages
27
Likes
1
I would check out File > Automate > Contact Sheet II as a starting point.
John Wheeler
That's almost perfect! The only problem is the maximum dimensions are 29,000 x 29,000px. I wonder if there's a way of tweaking that?
 

[ iLLuSioN ]

Power User
Messages
386
Likes
399
[...]but never been involved with PS Scripts
If you share a set of images (4-6) I can take a closer look what must changed...

*edit*
If you want to use the ContactSheet, you should take a closer look at the file "ContactSheetII.jsx" and change...
ContactSheetUI.MAX_PIXELS = 29000;
to your needs...
 
Last edited:

[ iLLuSioN ]

Power User
Messages
386
Likes
399
Thanks for the image set...

I noticed, that you use a bad naming for your files ... you have to try if it works..

JavaScript:
#target photoshop

var folder = Folder.selectDialog();
if (!folder) {alert("Cancelled"); exit;}

var origUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.POINTS;
var files = folder.getFiles(/\.(tif|tiff)$/i);
files.sort();
var numColGuess = Math.ceil(Math.sqrt(files.length));

var numCol = prompt("Found " + files.length + " images.\n" +
                    "How many columns are there?", numColGuess);
var numRows = Math.ceil(files.length / numCol);

var answer = prompt("Grid will be " + numCol + "x" + numRows + " tiles.\n" +
                    "Does x or y appear first in the filenames?", "x");
var orderedWithYFirst = (answer == "y" || answer == "Y");
if (!answer || numCol == 0 || numRows == 0) {alert("Bad values"); exit;}

var firstTile = app.open(File(files[0]));
if(app.activeDocument.mode==DocumentMode.INDEXEDCOLOR){
    activeDocument.changeMode (ChangeMode.CMYK);
    };
var tileWidth = firstTile.width;
var tileHeight = firstTile.height;

var firstLayer = firstTile.layers.length - 1;
firstTile.layers[firstLayer].name = files[0].name.slice(0,-4);
firstTile.resizeCanvas(firstTile.width * numCol,
                        firstTile.height * numRows,
                        AnchorPosition.BOTTOMLEFT);

doMenuItemNoInteraction = function(item) {
   var ref = new ActionReference();
   ref.putEnumerated(app.charIDToTypeID("Mn  "), app.charIDToTypeID("MnIt"), item);
   var desc = new ActionDescriptor();
   desc.putReference(app.charIDToTypeID("null"), ref);
   executeAction(app.stringIDToTypeID("select"), desc, DialogModes.NO);
}
doMenuItemNoInteraction(app.charIDToTypeID('FtOn'));

for (var i = 1; i < files.length; i++) {
  var tile = app.open(File(files[i]));
  if(app.activeDocument.mode==DocumentMode.INDEXEDCOLOR){
    activeDocument.changeMode (ChangeMode.CMYK);
    };
  if (tile.layers.length > 1) {
    tile.flatten();
  };
  tile.layers[0].duplicate(firstTile, ElementPlacement.PLACEATBEGINNING);
  tile.close(SaveOptions.DONOTSAVECHANGES);
  var layer = firstTile.layers[0];
  layer.name = files[i].name.slice(0,-4);
  var col = Math.floor(i / numRows);
  var row = i - (col * numRows);
  if (orderedWithYFirst) {
    row = Math.floor(i / numCol);
    col = i - (row * numCol);
  }
  var xOffset = (tileWidth * col) - layer.bounds[0];
  var yOffset = layer.bounds[3] - (tileHeight * row) ;
  layer.translate(xOffset, yOffset);
};
var basename = firstTile.name.match(/(.*)\.[^\.]+$/)[1];
var docPath = firstTile.path;
tifOpts = new TiffSaveOptions();
tifOpts.imageCompression = TIFFEncoding.TIFFLZW;
tifOpts.embedColorProfile = true;
tifOpts.alphaChannels = true;
tifOpts.layers = true;
tifOpts.byteOrder = ByteOrder.MACOS;
firstTile.saveAs((new File(docPath+'/'+basename.slice(0,-4)+"_comb.tif")),tifOpts,false);
app.preferences.rulerUnits = origUnits;

Where do these maps come from?
 

IanC

Active Member
Messages
27
Likes
1
If you want to use the ContactSheet, you should take a closer look at the file "ContactSheetII.jsx" and change...
ContactSheetUI.MAX_PIXELS = 29000;
to your needs...

OK, amending 29,000 to 40,000 works! My only problem now is that I can't seem to set the space between tiles to zero, it won't allow me to go lower than 1px.
 

IanC

Active Member
Messages
27
Likes
1
Thanks for the image set...

I noticed, that you use a bad naming for your files ... you have to try if it works..

Where do these maps come from?

Thanks! That's appeared just about perfect, but unfortunately with 100 tiles, although all appear to be opened in Photoshop in separate layers, about 80% is a transparency, not sure why? Just looked at the script, and wonder if it's due to tifOpts.byteOrder = ByteOrder.MACOS when I'm on a PC? But guess a TIF should be universal.

Screenshot 2020-11-12 115101.jpg

The maps are from the UK's mapping agency (Ordnance Survey). Hopefully I'm not infringing any copyright by sharing these in a coding forum (as opposed to a leisure/walking forum), as I have a licence for 'data exploration' for software development. The file-naming as is usual for maps is based on an XY grid, with 0,0 being bottom left rather than top left with image programmes. Anyway, full set of 100 tiles here:



Would also be good if the file saved simply as "SP.tif" whereas at present it's simply "_comb.tif", but looking at the code I think perhaps you'd intended it to be "SP_comb.tif"? I don't yet understand javascript, but guess this is a goodtime to learn!
 

[ iLLuSioN ]

Power User
Messages
386
Likes
399
For the filename you can change:
firstTile.saveAs((new File(docPath+'/'+basename.slice(0,-4)+"_comb.tif")),tifOpts,false);
to
firstTile.saveAs((new File(docPath+"/SP.tif")),tifOpts,false);

The Byteorder isn't a problem, but you can change it from...
tifOpts.byteOrder = ByteOrder.MACOS;
to
tifOpts.byteOrder = ByteOrder.IBM;

I didn't spend much time to the Movement of the tiles (OffsetX & OffsetY) - you can try it by your own.

Another problem will be the naming/sorting - if you use more then 100 images, there will be a problem...

1) sorting order
sp00.tif
sp01.tif
.
.
.
sp09.tif
sp10.tif
sp100.tif
sp101.tif

sp11.tif

Ok - you can ignore the sorting and use the filename itself, but what does sp212.tif means?

row 2 column 12 or row 21 column 2?

I suggest that you post your problem in the Adobe forum ...


... or wait till @Paul MR or @Dormeur74 find this thread.
 

thebestcpu

Guru
Messages
2,988
Likes
2,747
HI @IanC and @[ illusion ]
One thing that I noticed is that the incoming images are in "Indexed" mode (no doubt for a smaller file size). The script seems to indicate that when a tile comes in with Indexed mode it coverts it to CMYK (no idea why yet seemed like a yellow flag).
I do know that the max file size for TIFF file is 4GB and a regular RGB or CMYK 8 bit depth file of 40,000 x 40,000 pixels can potentially exceed that file size (may fit with compression).

More digging into the details might be required.

A couple things could be tried
Take you hundred file tiles, read them in and change to RGB mode instead of Indexed mode and save to TIFF and use ZIP mode instead of LZW. This ends up with tile files that are on the same size than the original indexed files.
Then rerun the script and see if you have any better results.

A second item is more of a question of your user needs. Do you actually need 40,000 x 40,000 pixels. If this is for use in some other map program and you need the original resolution I see the need. Yet if this is just for printing or regular viewing, unless you are creating a 10 foot x 10 foot print that needs to be viewed as close as ~12 inches over that entire area, the resolution may not need to be that high. That might solve some problems too.

Hope this information is helpful
John Wheeler
 

IanC

Active Member
Messages
27
Likes
1
HI @IanC and @[ illusion ]
One thing that I noticed is that the incoming images are in "Indexed" mode (no doubt for a smaller file size). The script seems to indicate that when a tile comes in with Indexed mode it coverts it to CMYK (no idea why yet seemed like a yellow flag).
I do know that the max file size for TIFF file is 4GB and a regular RGB or CMYK 8 bit depth file of 40,000 x 40,000 pixels can potentially exceed that file size (may fit with compression).

More digging into the details might be required.

A couple things could be tried
Take you hundred file tiles, read them in and change to RGB mode instead of Indexed mode and save to TIFF and use ZIP mode instead of LZW. This ends up with tile files that are on the same size than the original indexed files.
Then rerun the script and see if you have any better results.

A second item is more of a question of your user needs. Do you actually need 40,000 x 40,000 pixels. If this is for use in some other map program and you need the original resolution I see the need. Yet if this is just for printing or regular viewing, unless you are creating a 10 foot x 10 foot print that needs to be viewed as close as ~12 inches over that entire area, the resolution may not need to be that high. That might solve some problems too.

Hope this information is helpful
John Wheeler
That's weird, I'm showing RGB:

1605200362224.png

Anyway, I've just solved all of my problems! And learnt a bit in the process. Getting the sort order correct was the trickiest, though I hadn't expected it to be.

Happy to post the script if anyone would like it? Would like to tweak a couple of things first.
 

thebestcpu

Guru
Messages
2,988
Likes
2,747
@IanC Yes please post when you are done with the script. No doubt useful for others in the future.

Note here is what I opened after you shared the files on WeTransfer. Maybe WeTransfer compressed. This is for sp00.tif and also the next file did the same.
Moot point now that you have it working which is great
John Wheeler


Screen Shot 2020-11-12 at 10.59.34 AM.png
 

IanC

Active Member
Messages
27
Likes
1
OK, so here it is. Adapted a bit to suit my needs. Most suitable for stitching map tiles, where the filename includes XY coordinates, so the file sort starts at bottom left and finishes top right.

Code:
#target photoshop

var folder = Folder.selectDialog();
if (!folder) {alert("Cancelled"); exit;}

var origUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.POINTS;
var files = folder.getFiles(/\.(tif|tiff)$/i);
files.sort();
var numColGuess = Math.ceil(Math.sqrt(files.length));

var numCol = prompt("Found " + files.length + " images.\n" +
                    "How many columns are there?", numColGuess);
var numRows = Math.ceil(files.length / numCol);

var answer = prompt("Grid will be " + numCol + "x" + numRows + " tiles.\n" +
                    "Does X or Y appear first in the filenames?", "x");
var orderedWithYFirst = (answer == "y" || answer == "Y");
if (!answer || numCol == 0 || numRows == 0) {alert("Bad values"); exit;}

var firstTile = app.open(File(files[0]));
if(app.activeDocument.mode==DocumentMode.INDEXEDCOLOR){
    activeDocument.changeMode (ChangeMode.CMYK);
    };
var tileWidth = firstTile.width;
var tileHeight = firstTile.height;

var firstLayer = firstTile.layers.length - 1;
firstTile.layers[firstLayer].name = files[0].name.slice(2,4);
firstTile.resizeCanvas(firstTile.width * numCol,
                        firstTile.height * numRows,
                        AnchorPosition.BOTTOMLEFT);

doMenuItemNoInteraction = function(item) {
   var ref = new ActionReference();
   ref.putEnumerated(app.charIDToTypeID("Mn  "), app.charIDToTypeID("MnIt"), item);
   var desc = new ActionDescriptor();
   desc.putReference(app.charIDToTypeID("null"), ref);
   executeAction(app.stringIDToTypeID("select"), desc, DialogModes.NO);
}
doMenuItemNoInteraction(app.charIDToTypeID('FtOn'));

for (var i = 1; i < files.length; i++) {
  var tile = app.open(File(files[i]));
  if(app.activeDocument.mode==DocumentMode.INDEXEDCOLOR){
    activeDocument.changeMode (ChangeMode.CMYK);
    };
  if (tile.layers.length > 1) {
    tile.flatten();
  };
  tile.layers[0].duplicate(firstTile, ElementPlacement.PLACEATBEGINNING);
  tile.close(SaveOptions.DONOTSAVECHANGES);
  var layer = firstTile.layers[0];
  layer.name = files[i].name.slice(2,4);
  var col = Math.floor(i / numRows);
  var row = i - (col * numRows);
    if (orderedWithYFirst) {
    row = Math.floor(i / numCol);
    col = i - (row * numCol);
  }
  var xOffset = tileWidth * col;
  var yOffset = tileHeight * (numRows - row - 1);
  layer.translate(xOffset, yOffset);
};
var basename = firstTile.name.match(/(.*)\.[^\.]+$/)[1];
var docPath = firstTile.path;
tifOpts = new TiffSaveOptions();
tifOpts.imageCompression = TIFFEncoding.TIFFLZW;
tifOpts.embedColorProfile = false;
tifOpts.alphaChannels = false;
tifOpts.layers = false;
tifOpts.byteOrder = ByteOrder.IBM;
firstTile.saveAs((new File(docPath+'/'+basename.slice(0,2).toUpperCase())),tifOpts,false);
firstTile.close(SaveOptions.DONOTSAVECHANGES);
app.preferences.rulerUnits = origUnits;
Thanks for all the help, nice to have learned something new today!
 

Top