@@ -113,6 +113,9 @@ public class DicomWriter extends FormatWriter implements IExtraMetadataWriter {
113
113
private int baseTileHeight = 256 ;
114
114
private int [] tileWidth ;
115
115
private int [] tileHeight ;
116
+ private long [] tileWidthPointer ;
117
+ private long [] tileHeightPointer ;
118
+ private long [] tileCountPointer ;
116
119
private PlaneOffset [][] planeOffsets ;
117
120
private Integer currentPlane = null ;
118
121
private UIDCreator uids ;
@@ -232,14 +235,7 @@ public void saveCompressedBytes(int no, byte[] buf, int x, int y, int w, int h)
232
235
LOGGER .debug ("savePrecompressedBytes(series={}, resolution={}, no={}, x={}, y={})" ,
233
236
series , resolution , no , x , y );
234
237
235
- // TODO: may want better handling of non-tiled "extra" images (e.g. label, macro)
236
238
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
- }
243
239
244
240
int bytesPerPixel = FormatTools .getBytesPerPixel (
245
241
FormatTools .pixelTypeFromString (
@@ -279,6 +275,13 @@ public void saveCompressedBytes(int no, byte[] buf, int x, int y, int w, int h)
279
275
boolean first = x == 0 && y == 0 ;
280
276
boolean last = x + w == getSizeX () && y + h == getSizeY ();
281
277
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
+
282
285
// the compression type isn't supplied to the writer until
283
286
// after setId is called, so metadata that indicates or
284
287
// 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)
296
299
if (getTIFFCompression () == TiffCompression .JPEG ) {
297
300
ifds [resolutionIndex ][no ].put (IFD .PHOTOMETRIC_INTERPRETATION , PhotoInterp .Y_CB_CR .getCode ());
298
301
}
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 ))));
299
311
}
300
312
301
313
out .seek (out .length ());
@@ -334,6 +346,17 @@ public void saveCompressedBytes(int no, byte[] buf, int x, int y, int w, int h)
334
346
if (ifds [resolutionIndex ][no ] != null ) {
335
347
tileByteCounts = (long []) ifds [resolutionIndex ][no ].getIFDValue (IFD .TILE_BYTE_COUNTS );
336
348
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
+ }
337
360
}
338
361
339
362
if (tileByteCounts != null ) {
@@ -367,13 +390,7 @@ public void saveBytes(int no, byte[] buf, int x, int y, int w, int h)
367
390
int thisTileHeight = tileHeight [resolutionIndex ];
368
391
369
392
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 ||
377
394
(w != thisTileWidth && x + w != getSizeX ()) ||
378
395
(h != thisTileHeight && y + h != getSizeY ()))
379
396
{
@@ -385,6 +402,10 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
385
402
boolean first = x == 0 && y == 0 ;
386
403
boolean last = x + w == getSizeX () && y + h == getSizeY ();
387
404
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
+
388
409
// the compression type isn't supplied to the writer until
389
410
// after setId is called, so metadata that indicates or
390
411
// depends on the compression type needs to be set in
@@ -406,6 +427,15 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
406
427
ifds [resolutionIndex ][no ].put (IFD .PHOTOMETRIC_INTERPRETATION , PhotoInterp .Y_CB_CR .getCode ());
407
428
}
408
429
}
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 ))));
409
439
}
410
440
411
441
// TILED_SPARSE, so the tile coordinates must be written
@@ -498,7 +528,6 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
498
528
// in the IFD
499
529
// this tries to calculate the index without assuming sequential tile
500
530
// writing, but maybe there is a better way to calculate this?
501
- int xTiles = (int ) Math .ceil ((double ) getSizeX () / tileWidth [resolutionIndex ]);
502
531
int xTile = x / tileWidth [resolutionIndex ];
503
532
int yTile = y / tileHeight [resolutionIndex ];
504
533
int tileIndex = (yTile * xTiles ) + xTile ;
@@ -508,6 +537,17 @@ else if (x % thisTileWidth != 0 || y % thisTileHeight != 0 ||
508
537
if (ifds [resolutionIndex ][no ] != null ) {
509
538
tileByteCounts = (long []) ifds [resolutionIndex ][no ].getIFDValue (IFD .TILE_BYTE_COUNTS );
510
539
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
+ }
511
551
}
512
552
513
553
if (compression == null || compression .equals (CompressionType .UNCOMPRESSED .getCompression ())) {
@@ -640,6 +680,9 @@ public void setId(String id) throws FormatException, IOException {
640
680
planeOffsets = new PlaneOffset [totalFiles ][];
641
681
tileWidth = new int [totalFiles ];
642
682
tileHeight = new int [totalFiles ];
683
+ tileWidthPointer = new long [totalFiles ];
684
+ tileHeightPointer = new long [totalFiles ];
685
+ tileCountPointer = new long [totalFiles ];
643
686
644
687
// create UIDs that must be consistent across all files in the dataset
645
688
String specimenUIDValue = uids .getUID ();
@@ -739,8 +782,9 @@ public void setId(String id) throws FormatException, IOException {
739
782
int tileCountX = (int ) Math .ceil ((double ) width / tileWidth [resolutionIndex ]);
740
783
int tileCountY = (int ) Math .ceil ((double ) height / tileHeight [resolutionIndex ]);
741
784
DicomTag numberOfFrames = new DicomTag (NUMBER_OF_FRAMES , IS );
785
+ // save space for up to 10 digits
742
786
numberOfFrames .value = padString (String .valueOf (
743
- tileCountX * tileCountY * sizeZ * r .getChannelCount (pyramid )));
787
+ tileCountX * tileCountY * sizeZ * r .getChannelCount (pyramid )), " " , 10 );
744
788
tags .add (numberOfFrames );
745
789
746
790
DicomTag matrixFrames = new DicomTag (TOTAL_PIXEL_MATRIX_FOCAL_PLANES , UL );
@@ -1374,6 +1418,9 @@ public void close() throws IOException {
1374
1418
ifds = null ;
1375
1419
tiffSaver = null ;
1376
1420
validPixelCount = null ;
1421
+ tileWidthPointer = null ;
1422
+ tileHeightPointer = null ;
1423
+ tileCountPointer = null ;
1377
1424
1378
1425
tagProviders .clear ();
1379
1426
@@ -1382,33 +1429,46 @@ public void close() throws IOException {
1382
1429
1383
1430
@ Override
1384
1431
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
1389
1432
if (currentId == null ) {
1390
1433
baseTileWidth = tileSize ;
1434
+ return baseTileWidth ;
1391
1435
}
1392
- return baseTileWidth ;
1436
+
1437
+ int resolutionIndex = getIndex (series , resolution );
1438
+ tileWidth [resolutionIndex ] = tileSize ;
1439
+ return tileWidth [resolutionIndex ];
1393
1440
}
1394
1441
1395
1442
@ Override
1396
1443
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 ];
1398
1450
}
1399
1451
1400
1452
@ Override
1401
1453
public int setTileSizeY (int tileSize ) throws FormatException {
1402
- // TODO: see note in setTileSizeX above
1403
1454
if (currentId == null ) {
1404
1455
baseTileHeight = tileSize ;
1456
+ return baseTileHeight ;
1405
1457
}
1406
- return baseTileHeight ;
1458
+
1459
+ int resolutionIndex = getIndex (series , resolution );
1460
+ tileHeight [resolutionIndex ] = tileSize ;
1461
+ return tileHeight [resolutionIndex ];
1407
1462
}
1408
1463
1409
1464
@ Override
1410
1465
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 ];
1412
1472
}
1413
1473
1414
1474
// -- DicomWriter-specific methods --
@@ -1468,15 +1528,25 @@ private void writeTag(DicomTag tag) throws IOException {
1468
1528
out .writeShort ((short ) getStoredLength (tag ));
1469
1529
}
1470
1530
1531
+ int resolutionIndex = getIndex (series , resolution );
1471
1532
if (tag .attribute == TRANSFER_SYNTAX_UID ) {
1472
- transferSyntaxPointer [getIndex ( series , resolution ) ] = out .getFilePointer ();
1533
+ transferSyntaxPointer [resolutionIndex ] = out .getFilePointer ();
1473
1534
}
1474
1535
else if (tag .attribute == LOSSY_IMAGE_COMPRESSION_METHOD ) {
1475
- compressionMethodPointer [getIndex ( series , resolution ) ] = out .getFilePointer ();
1536
+ compressionMethodPointer [resolutionIndex ] = out .getFilePointer ();
1476
1537
}
1477
1538
else if (tag .attribute == FILE_META_INFO_GROUP_LENGTH ) {
1478
1539
fileMetaLengthPointer = out .getFilePointer ();
1479
1540
}
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
+ }
1480
1550
1481
1551
// sequences with no items still need to write a SequenceDelimitationItem below
1482
1552
if (tag .children .size () == 0 && tag .value == null && tag .vr != SQ ) {
@@ -1665,6 +1735,17 @@ private String padString(String value, String append) {
1665
1735
return value + append ;
1666
1736
}
1667
1737
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
+
1668
1749
/**
1669
1750
* @return transfer syntax UID corresponding to the current compression type
1670
1751
*/
@@ -1919,6 +2000,9 @@ private void writeIFDs(int resIndex) throws IOException {
1919
2000
out .seek (ifdStart );
1920
2001
1921
2002
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
+
1922
2006
try {
1923
2007
tiffSaver .writeIFD (ifds [resIndex ][no ], 0 , no < ifds [resIndex ].length - 1 );
1924
2008
}
0 commit comments