Skip to content

Commit 6a4c93b

Browse files
authored
Merge pull request PolusAI#392 from Nicholas-Schaub/fix/tiffconverter
Re-added the tiled tiff connverter file.
2 parents 1fd8a6f + eb7f080 commit 6a4c93b

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package axle.polus.data.utils.converters;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.nio.file.Path;
6+
import java.nio.file.Paths;
7+
import java.util.logging.Level;
8+
import java.util.logging.Logger;
9+
10+
import loci.common.services.DependencyException;
11+
import loci.common.services.ServiceException;
12+
import loci.common.services.ServiceFactory;
13+
import loci.formats.FormatException;
14+
import loci.formats.FormatTools;
15+
import loci.formats.ImageReader;
16+
import loci.formats.codec.CompressionType;
17+
import loci.formats.meta.IMetadata;
18+
import loci.formats.out.OMETiffWriter;
19+
import loci.formats.services.OMEXMLService;
20+
import ome.xml.model.primitives.PositiveInteger;
21+
22+
/**
23+
* Based off of the example from
24+
* https://docs.openmicroscopy.org/bio-formats/5.9.1/developers/tiling.html
25+
*
26+
* One optimization this method uses is reading/writing multiple tiles
27+
* simultaneously (up to 16 at a time). This can be set using the xMulti and
28+
* yMulti variables.
29+
*
30+
* @author nick.schaub at nih.gov
31+
*/
32+
public class TiledOmeTiffConverter implements Runnable {
33+
/**
34+
* Class that handles conversion of any Bioformats supported image format into an OME tiled tiff.
35+
*/
36+
37+
private static final Logger LOG = Logger.getLogger(TiledOmeTiffConverter.class.getName());
38+
39+
private ImageReader reader;
40+
private String inputFile;
41+
private String outputFile;
42+
private int tileSizeX = 1024;
43+
private int tileSizeY = 1024;
44+
private int xMulti = 4;
45+
private int yMulti = 4;
46+
private int Z;
47+
private int C;
48+
private int T;
49+
50+
/**
51+
* Class constructor
52+
*
53+
* @param reader ImageReader to convert to .ome.tif
54+
* @param outputFile Complete path to export file
55+
* @param Z The z-slice to export
56+
* @param C The channel slice to export
57+
* @param T The time-point slice to export
58+
*/
59+
public TiledOmeTiffConverter(ImageReader reader, String outputFile, int Z, int C, int T) {
60+
this.inputFile = reader.getCurrentFile();
61+
this.outputFile = outputFile;
62+
this.Z = Z;
63+
this.C = C;
64+
this.T = T;
65+
}
66+
67+
/**
68+
* Initialize the OME Tiff writer
69+
*
70+
* @param omexml Base metadata to import
71+
* @return
72+
* @throws FormatException
73+
* @throws IOException
74+
*/
75+
public OMETiffWriter init_writer(IMetadata omexml) {
76+
77+
// important to delete because OME uses RandomAccessFile
78+
Path outputPath = Paths.get(this.outputFile);
79+
outputPath.toFile().delete();
80+
81+
// set up the writer and associate it with the output file
82+
OMETiffWriter writer = new OMETiffWriter();
83+
writer.setMetadataRetrieve(omexml);
84+
85+
// set output file properties
86+
try {
87+
this.tileSizeX = writer.setTileSizeX(tileSizeX);
88+
this.tileSizeY = writer.setTileSizeY(tileSizeY);
89+
writer.setId(this.outputFile);
90+
writer.setCompression(CompressionType.LZW.getCompression());
91+
} catch (FormatException | IOException e) {
92+
// TODO Auto-generated catch block
93+
e.printStackTrace();
94+
}
95+
96+
omexml = (IMetadata) writer.getMetadataRetrieve();
97+
omexml.setPixelsSizeZ(new PositiveInteger(1), 0);
98+
omexml.setPixelsSizeC(new PositiveInteger(1), 0);
99+
omexml.setPixelsSizeT(new PositiveInteger(1), 0);
100+
101+
return writer;
102+
}
103+
104+
/**
105+
* Read input file and write output file in tiles
106+
*
107+
* The input files is read in tiles of size (tileSizeX, tileSizeY), with
108+
* only one z-slice at a time. Each channel and time-point (if present) are
109+
* saved to separate files.
110+
*
111+
* It is important to save images in tiles, since Bioformats indexes pixels
112+
* using signed integers (32 bits). This means that loading a full image
113+
* plane larger than 2GB will throw an indexing error. Saving in tiles also
114+
* has the benefit of being memory efficient, so it can be run on small
115+
* nodes.
116+
*
117+
* @throws FormatException
118+
* @throws DependencyException
119+
* @throws ServiceException
120+
* @throws IOException
121+
*/
122+
public void readWriteTiles() throws FormatException, DependencyException, ServiceException, IOException {
123+
LOG.info("Writing " + (new File(this.outputFile)).getName() + " from " + (new File(this.inputFile)).getName());
124+
125+
// construct the object that stores OME-XML metadata
126+
ServiceFactory factory = new ServiceFactory();
127+
OMEXMLService service = factory.getInstance(OMEXMLService.class);
128+
IMetadata omexml = service.createOMEXMLMetadata();
129+
130+
// set up the reader and associate it with the input file
131+
reader = new ImageReader();
132+
reader.setOriginalMetadataPopulated(true);
133+
reader.setMetadataStore(omexml);
134+
reader.setId(this.inputFile);
135+
136+
int bpp = FormatTools.getBytesPerPixel(reader.getPixelType());
137+
int tilePlaneSize = xMulti * yMulti * tileSizeX * tileSizeY * reader.getRGBChannelCount() * bpp;
138+
byte[] buf = new byte[tilePlaneSize];
139+
140+
OMETiffWriter writer = this.init_writer(omexml);
141+
142+
int width = reader.getSizeX();
143+
int height = reader.getSizeY();
144+
145+
// Determined the number of tiles to read and write
146+
int nXTiles = width / (xMulti * tileSizeX);
147+
int nYTiles = height / (yMulti * tileSizeY);
148+
if (nXTiles * tileSizeX * xMulti != width) nXTiles++;
149+
if (nYTiles * tileSizeY * yMulti != height) nYTiles++;
150+
151+
int index = reader.getIndex(this.Z, this.C, this.T);
152+
for (int y=0; y<nYTiles; y++) {
153+
for (int x=0; x<nXTiles; x++) {
154+
155+
int tileX = x * tileSizeX * xMulti;
156+
int tileY = y * tileSizeY * yMulti;
157+
158+
159+
int effTileSizeX = (tileX + tileSizeX * xMulti) < width ? tileSizeX * xMulti: width - tileX;
160+
int effTileSizeY = (tileY + tileSizeY * yMulti) < height ? tileSizeY * yMulti : height - tileY;
161+
162+
buf = reader.openBytes(index, tileX, tileY, effTileSizeX, effTileSizeY);
163+
writer.saveBytes(0, buf, tileX, tileY, effTileSizeX, effTileSizeY);
164+
}
165+
}
166+
167+
// Close the readers/writers
168+
try {
169+
writer.close();
170+
}
171+
catch (IOException e) {
172+
LOG.log(Level.WARNING, reader.toString() + ": Failed to close writer.",e);
173+
}
174+
try {
175+
reader.close();
176+
}
177+
catch (IOException e) {
178+
LOG.log(Level.WARNING, reader.toString() + ": Failed to close reader.",e);
179+
}
180+
}
181+
182+
/**
183+
* Main process, set up for threading
184+
*/
185+
public void run() {
186+
187+
try {
188+
// read and write the image using tiles
189+
this.readWriteTiles();
190+
}
191+
catch(Exception e) {
192+
System.err.println("Failed to read and write tiles for image: " + new File(this.outputFile).getName());
193+
e.printStackTrace();
194+
}
195+
}
196+
197+
}

0 commit comments

Comments
 (0)