@@ -113,6 +113,9 @@ public class DicomWriter extends FormatWriter implements IExtraMetadataWriter {
113113 private int baseTileHeight = 256 ;
114114 private int [] tileWidth ;
115115 private int [] tileHeight ;
116+ private long [] tileWidthPointer ;
117+ private long [] tileHeightPointer ;
118+ private long [] tileCountPointer ;
116119 private PlaneOffset [][] planeOffsets ;
117120 private Integer currentPlane = null ;
118121 private UIDCreator uids ;
@@ -232,14 +235,7 @@ public void saveCompressedBytes(int no, byte[] buf, int x, int y, int w, int h)
232235 LOGGER .debug ("savePrecompressedBytes(series={}, resolution={}, no={}, x={}, y={})" ,
233236 series , resolution , no , x , y );
234237
235- // TODO: may want better handling of non-tiled "extra" images (e.g. label, macro)
236238 MetadataRetrieve r = getMetadataRetrieve ();
237- if ((!(r instanceof IPyramidStore ) ||
238- ((IPyramidStore ) r ).getResolutionCount (series ) == 1 ) &&
239- !isFullPlane (x , y , w , h ))
240- {
241- throw new FormatException ("DicomWriter does not allow tiles for non-pyramid images" );
242- }
243239
244240 int bytesPerPixel = FormatTools .getBytesPerPixel (
245241 FormatTools .pixelTypeFromString (
@@ -279,6 +275,13 @@ public void saveCompressedBytes(int no, byte[] buf, int x, int y, int w, int h)
279275 boolean first = x == 0 && y == 0 ;
280276 boolean last = x + w == getSizeX () && y + h == getSizeY ();
281277
278+ int width = getSizeX ();
279+ int height = getSizeY ();
280+ int sizeZ = r .getPixelsSizeZ (series ).getValue ().intValue ();
281+
282+ int tileCountX = (int ) Math .ceil ((double ) width / tileWidth [resolutionIndex ]);
283+ int tileCountY = (int ) Math .ceil ((double ) height / tileHeight [resolutionIndex ]);
284+
282285 // the compression type isn't supplied to the writer until
283286 // after setId is called, so metadata that indicates or
284287 // depends on the compression type needs to be set in
@@ -296,6 +299,15 @@ public void saveCompressedBytes(int no, byte[] buf, int x, int y, int w, int h)
296299 if (getTIFFCompression () == TiffCompression .JPEG ) {
297300 ifds [resolutionIndex ][no ].put (IFD .PHOTOMETRIC_INTERPRETATION , PhotoInterp .Y_CB_CR .getCode ());
298301 }
302+
303+ out .seek (tileWidthPointer [resolutionIndex ]);
304+ out .writeShort ((short ) getTileSizeX ());
305+ out .seek (tileHeightPointer [resolutionIndex ]);
306+ out .writeShort ((short ) getTileSizeY ());
307+ out .seek (tileCountPointer [resolutionIndex ]);
308+
309+ out .writeBytes (padString (String .valueOf (
310+ tileCountX * tileCountY * sizeZ * r .getChannelCount (series ))));
299311 }
300312
301313 out .seek (out .length ());
@@ -334,6 +346,17 @@ public void saveCompressedBytes(int no, byte[] buf, int x, int y, int w, int h)
334346 if (ifds [resolutionIndex ][no ] != null ) {
335347 tileByteCounts = (long []) ifds [resolutionIndex ][no ].getIFDValue (IFD .TILE_BYTE_COUNTS );
336348 tileOffsets = (long []) ifds [resolutionIndex ][no ].getIFDValue (IFD .TILE_OFFSETS );
349+
350+ if (tileByteCounts .length < tileCountX * tileCountY ) {
351+ long [] newTileByteCounts = new long [tileCountX * tileCountY ];
352+ long [] newTileOffsets = new long [tileCountX * tileCountY ];
353+ System .arraycopy (tileByteCounts , 0 , newTileByteCounts , 0 , tileByteCounts .length );
354+ System .arraycopy (tileOffsets , 0 , newTileOffsets , 0 , tileOffsets .length );
355+ tileByteCounts = newTileByteCounts ;
356+ tileOffsets = newTileOffsets ;
357+ ifds [resolutionIndex ][no ].put (IFD .TILE_BYTE_COUNTS , tileByteCounts );
358+ ifds [resolutionIndex ][no ].put (IFD .TILE_OFFSETS , tileOffsets );
359+ }
337360 }
338361
339362 if (tileByteCounts != null ) {
@@ -367,13 +390,7 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h)
367390 int thisTileHeight = tileHeight [resolutionIndex ];
368391
369392 MetadataRetrieve r = getMetadataRetrieve ();
370- if ((!(r instanceof IPyramidStore ) ||
371- ((IPyramidStore ) r ).getResolutionCount (series ) == 1 ) &&
372- !isFullPlane (x , y , w , h ))
373- {
374- throw new FormatException ("DicomWriter does not allow tiles for non-pyramid images" );
375- }
376- else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
393+ if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
377394 (w != thisTileWidth && x + w != getSizeX ()) ||
378395 (h != thisTileHeight && y + h != getSizeY ()))
379396 {
@@ -385,6 +402,10 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
385402 boolean first = x == 0 && y == 0 ;
386403 boolean last = x + w == getSizeX () && y + h == getSizeY ();
387404
405+ int xTiles = (int ) Math .ceil ((double ) getSizeX () / thisTileWidth );
406+ int yTiles = (int ) Math .ceil ((double ) getSizeY () / thisTileHeight );
407+ int sizeZ = r .getPixelsSizeZ (series ).getValue ().intValue ();
408+
388409 // the compression type isn't supplied to the writer until
389410 // after setId is called, so metadata that indicates or
390411 // depends on the compression type needs to be set in
@@ -406,6 +427,15 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
406427 ifds [resolutionIndex ][no ].put (IFD .PHOTOMETRIC_INTERPRETATION , PhotoInterp .Y_CB_CR .getCode ());
407428 }
408429 }
430+
431+ out .seek (tileWidthPointer [resolutionIndex ]);
432+ out .writeShort ((short ) getTileSizeX ());
433+ out .seek (tileHeightPointer [resolutionIndex ]);
434+ out .writeShort ((short ) getTileSizeY ());
435+ out .seek (tileCountPointer [resolutionIndex ]);
436+
437+ out .writeBytes (padString (String .valueOf (
438+ xTiles * yTiles * sizeZ * r .getChannelCount (series ))));
409439 }
410440
411441 // TILED_SPARSE, so the tile coordinates must be written
@@ -498,7 +528,6 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
498528 // in the IFD
499529 // this tries to calculate the index without assuming sequential tile
500530 // writing, but maybe there is a better way to calculate this?
501- int xTiles = (int ) Math .ceil ((double ) getSizeX () / tileWidth [resolutionIndex ]);
502531 int xTile = x / tileWidth [resolutionIndex ];
503532 int yTile = y / tileHeight [resolutionIndex ];
504533 int tileIndex = (yTile * xTiles ) + xTile ;
@@ -508,6 +537,17 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
508537 if (ifds [resolutionIndex ][no ] != null ) {
509538 tileByteCounts = (long []) ifds [resolutionIndex ][no ].getIFDValue (IFD .TILE_BYTE_COUNTS );
510539 tileOffsets = (long []) ifds [resolutionIndex ][no ].getIFDValue (IFD .TILE_OFFSETS );
540+
541+ if (tileByteCounts .length < xTiles * yTiles ) {
542+ long [] newTileByteCounts = new long [xTiles * yTiles ];
543+ long [] newTileOffsets = new long [xTiles * yTiles ];
544+ System .arraycopy (tileByteCounts , 0 , newTileByteCounts , 0 , tileByteCounts .length );
545+ System .arraycopy (tileOffsets , 0 , newTileOffsets , 0 , tileOffsets .length );
546+ tileByteCounts = newTileByteCounts ;
547+ tileOffsets = newTileOffsets ;
548+ ifds [resolutionIndex ][no ].put (IFD .TILE_BYTE_COUNTS , tileByteCounts );
549+ ifds [resolutionIndex ][no ].put (IFD .TILE_OFFSETS , tileOffsets );
550+ }
511551 }
512552
513553 if (compression == null || compression .equals (CompressionType .UNCOMPRESSED .getCompression ())) {
@@ -640,6 +680,9 @@ public void setId(String id) throws FormatException, IOException {
640680 planeOffsets = new PlaneOffset [totalFiles ][];
641681 tileWidth = new int [totalFiles ];
642682 tileHeight = new int [totalFiles ];
683+ tileWidthPointer = new long [totalFiles ];
684+ tileHeightPointer = new long [totalFiles ];
685+ tileCountPointer = new long [totalFiles ];
643686
644687 // create UIDs that must be consistent across all files in the dataset
645688 String specimenUIDValue = uids .getUID ();
@@ -739,8 +782,9 @@ public void setId(String id) throws FormatException, IOException {
739782 int tileCountX = (int ) Math .ceil ((double ) width / tileWidth [resolutionIndex ]);
740783 int tileCountY = (int ) Math .ceil ((double ) height / tileHeight [resolutionIndex ]);
741784 DicomTag numberOfFrames = new DicomTag (NUMBER_OF_FRAMES , IS );
785+ // save space for up to 10 digits
742786 numberOfFrames .value = padString (String .valueOf (
743- tileCountX * tileCountY * sizeZ * r .getChannelCount (pyramid )));
787+ tileCountX * tileCountY * sizeZ * r .getChannelCount (pyramid )), " " , 10 );
744788 tags .add (numberOfFrames );
745789
746790 DicomTag matrixFrames = new DicomTag (TOTAL_PIXEL_MATRIX_FOCAL_PLANES , UL );
@@ -1374,6 +1418,9 @@ public void close() throws IOException {
13741418 ifds = null ;
13751419 tiffSaver = null ;
13761420 validPixelCount = null ;
1421+ tileWidthPointer = null ;
1422+ tileHeightPointer = null ;
1423+ tileCountPointer = null ;
13771424
13781425 tagProviders .clear ();
13791426
@@ -1382,33 +1429,46 @@ public void close() throws IOException {
13821429
13831430 @ Override
13841431 public int setTileSizeX (int tileSize ) throws FormatException {
1385- // TODO: this currently enforces the same tile size across all resolutions
1386- // since the tile size is written during setId
1387- // the tile size should probably be configurable per resolution,
1388- // for better pre-compressed tile support
13891432 if (currentId == null ) {
13901433 baseTileWidth = tileSize ;
1434+ return baseTileWidth ;
13911435 }
1392- return baseTileWidth ;
1436+
1437+ int resolutionIndex = getIndex (series , resolution );
1438+ tileWidth [resolutionIndex ] = tileSize ;
1439+ return tileWidth [resolutionIndex ];
13931440 }
13941441
13951442 @ Override
13961443 public int getTileSizeX () {
1397- return baseTileWidth ;
1444+ if (currentId == null ) {
1445+ return baseTileWidth ;
1446+ }
1447+
1448+ int resolutionIndex = getIndex (series , resolution );
1449+ return tileWidth [resolutionIndex ];
13981450 }
13991451
14001452 @ Override
14011453 public int setTileSizeY (int tileSize ) throws FormatException {
1402- // TODO: see note in setTileSizeX above
14031454 if (currentId == null ) {
14041455 baseTileHeight = tileSize ;
1456+ return baseTileHeight ;
14051457 }
1406- return baseTileHeight ;
1458+
1459+ int resolutionIndex = getIndex (series , resolution );
1460+ tileHeight [resolutionIndex ] = tileSize ;
1461+ return tileHeight [resolutionIndex ];
14071462 }
14081463
14091464 @ Override
14101465 public int getTileSizeY () {
1411- return baseTileHeight ;
1466+ if (currentId == null ) {
1467+ return baseTileHeight ;
1468+ }
1469+
1470+ int resolutionIndex = getIndex (series , resolution );
1471+ return tileHeight [resolutionIndex ];
14121472 }
14131473
14141474 // -- DicomWriter-specific methods --
@@ -1468,15 +1528,25 @@ private void writeTag(DicomTag tag) throws IOException {
14681528 out .writeShort ((short ) getStoredLength (tag ));
14691529 }
14701530
1531+ int resolutionIndex = getIndex (series , resolution );
14711532 if (tag .attribute == TRANSFER_SYNTAX_UID ) {
1472- transferSyntaxPointer [getIndex ( series , resolution ) ] = out .getFilePointer ();
1533+ transferSyntaxPointer [resolutionIndex ] = out .getFilePointer ();
14731534 }
14741535 else if (tag .attribute == LOSSY_IMAGE_COMPRESSION_METHOD ) {
1475- compressionMethodPointer [getIndex ( series , resolution ) ] = out .getFilePointer ();
1536+ compressionMethodPointer [resolutionIndex ] = out .getFilePointer ();
14761537 }
14771538 else if (tag .attribute == FILE_META_INFO_GROUP_LENGTH ) {
14781539 fileMetaLengthPointer = out .getFilePointer ();
14791540 }
1541+ else if (tag .attribute == ROWS ) {
1542+ tileHeightPointer [resolutionIndex ] = out .getFilePointer ();
1543+ }
1544+ else if (tag .attribute == COLUMNS ) {
1545+ tileWidthPointer [resolutionIndex ] = out .getFilePointer ();
1546+ }
1547+ else if (tag .attribute == NUMBER_OF_FRAMES ) {
1548+ tileCountPointer [resolutionIndex ] = out .getFilePointer ();
1549+ }
14801550
14811551 // sequences with no items still need to write a SequenceDelimitationItem below
14821552 if (tag .children .size () == 0 && tag .value == null && tag .vr != SQ ) {
@@ -1665,6 +1735,17 @@ private String padString(String value, String append) {
16651735 return value + append ;
16661736 }
16671737
1738+ private String padString (String value , String append , int length ) {
1739+ String rtn = "" ;
1740+ if (value != null ) {
1741+ rtn += value ;
1742+ }
1743+ while (rtn .length () < length ) {
1744+ rtn += append ;
1745+ }
1746+ return rtn ;
1747+ }
1748+
16681749 /**
16691750 * @return transfer syntax UID corresponding to the current compression type
16701751 */
@@ -1919,6 +2000,9 @@ private void writeIFDs(int resIndex) throws IOException {
19192000 out .seek (ifdStart );
19202001
19212002 for (int no =0 ; no <ifds [resIndex ].length ; no ++) {
2003+ ifds [resIndex ][no ].put (IFD .TILE_WIDTH , tileWidth [resIndex ]);
2004+ ifds [resIndex ][no ].put (IFD .TILE_LENGTH , tileHeight [resIndex ]);
2005+
19222006 try {
19232007 tiffSaver .writeIFD (ifds [resIndex ][no ], 0 , no < ifds [resIndex ].length - 1 );
19242008 }
0 commit comments