Skip to content
Commits on Source (7)
repo: 3959d33612ccaadc0d4d707227fbed09ac35e5fe
node: 56d7f3d50c89c6c66c9932621a1ae05403e34ee1
branch: Orthanc-1.5.6
node: 90140b7796a1738a525f3a9d1ef404cb35e5e2c7
branch: Orthanc-1.5.7
latesttag: dcmtk-3.6.1
latesttagdistance: 817
changessincelatesttag: 934
latesttagdistance: 938
changessincelatesttag: 1066
......@@ -4,4 +4,5 @@ CMakeLists.txt.user
*.cpp.orig
*.h.orig
.vs/
.vscode/
*~
......@@ -115,6 +115,7 @@ set(ORTHANC_UNIT_TESTS_SOURCES
UnitTestsSources/ImageProcessingTests.cpp
UnitTestsSources/ImageTests.cpp
UnitTestsSources/JpegLosslessTests.cpp
UnitTestsSources/LoggingTests.cpp
UnitTestsSources/LuaTests.cpp
UnitTestsSources/MemoryCacheTests.cpp
UnitTestsSources/MultiThreadingTests.cpp
......@@ -123,6 +124,7 @@ set(ORTHANC_UNIT_TESTS_SOURCES
UnitTestsSources/SQLiteTests.cpp
UnitTestsSources/ServerIndexTests.cpp
UnitTestsSources/StreamTests.cpp
UnitTestsSources/ToolboxTests.cpp
UnitTestsSources/UnitTestsMain.cpp
UnitTestsSources/VersionsTests.cpp
UnitTestsSources/ZipTests.cpp
......@@ -192,6 +194,7 @@ if (STANDALONE_BUILD)
${ORTHANC_EMBEDDED_FILES}
ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer
${DCMTK_DICTIONARIES}
${LIBICU_RESOURCES}
)
else()
add_definitions(
......@@ -200,6 +203,7 @@ else()
)
EmbedResources(
${ORTHANC_EMBEDDED_FILES}
${LIBICU_RESOURCES}
)
endif()
......@@ -332,6 +336,7 @@ add_executable(UnitTests
${GOOGLE_TEST_SOURCES}
${ORTHANC_UNIT_TESTS_PCH}
${ORTHANC_UNIT_TESTS_SOURCES}
${BOOST_EXTENDED_SOURCES}
)
target_link_libraries(UnitTests
......
......@@ -77,6 +77,19 @@ namespace Orthanc
}
void ChunkedBuffer::AddChunkDestructive(std::string& chunk)
{
size_t chunkSize = chunk.size();
if (chunkSize > 0)
{
chunks_.push_back(new std::string);
chunks_.back()->swap(chunk);
numBytes_ += chunkSize;
}
}
void ChunkedBuffer::Flatten(std::string& result)
{
result.resize(numBytes_);
......
......@@ -67,6 +67,9 @@ namespace Orthanc
void AddChunk(const std::string& chunk);
// The source content will be emptied
void AddChunkDestructive(std::string& chunk);
void Flatten(std::string& result);
};
}
......@@ -91,7 +91,9 @@ namespace Orthanc
const void* uncompressed,
size_t uncompressedSize)
{
uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */;
uLongf compressedSize = compressBound(static_cast<uLong>(uncompressedSize))
+ 1024 /* security margin */;
if (compressedSize == 0)
{
compressedSize = 1;
......
......@@ -53,7 +53,8 @@ namespace Orthanc
return;
}
uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */;
uLongf compressedSize = compressBound(static_cast<uLong>(uncompressedSize))
+ 1024 /* security margin */;
if (compressedSize == 0)
{
compressedSize = 1;
......@@ -74,7 +75,7 @@ namespace Orthanc
int error = compress2(target,
&compressedSize,
const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)),
uncompressedSize,
static_cast<uLong>(uncompressedSize),
GetCompressionLevel());
if (error != Z_OK)
......@@ -137,7 +138,7 @@ namespace Orthanc
(reinterpret_cast<uint8_t*>(&uncompressed[0]),
&tmp,
reinterpret_cast<const uint8_t*>(compressed) + sizeof(uint64_t),
compressedSize - sizeof(uint64_t));
static_cast<uLong>(compressedSize - sizeof(uint64_t)));
if (error != Z_OK)
{
......
......@@ -328,7 +328,7 @@ namespace Orthanc
if (cond.bad())
{
throw OrthancException(ErrorCode_DicomPortInUse,
"cannot create network: " + std::string(cond.text()));
" (port = " + boost::lexical_cast<std::string>(port_) + ") cannot create network: " + std::string(cond.text()));
}
continue_ = true;
......
......@@ -92,9 +92,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "../DicomParsing/FromDcmtkBridge.h"
#include "../DicomParsing/ToDcmtkBridge.h"
#include <dcmtk/dcmdata/dcdeftag.h>
#include <dcmtk/dcmdata/dcfilefo.h>
#include <dcmtk/dcmdata/dcistrmb.h>
#include <dcmtk/dcmdata/dcistrmf.h>
#include <dcmtk/dcmdata/dcfilefo.h>
#include <dcmtk/dcmdata/dcmetinf.h>
#include <dcmtk/dcmnet/diutil.h>
......@@ -316,9 +317,8 @@ namespace Orthanc
connection.remoteAet_, "C-STORE");
// Determine the storage SOP class UID for this instance
static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016);
OFString sopClassUid;
if (dcmff.getDataset()->findAndGetOFString(DCM_SOP_CLASS_UID, sopClassUid).good())
if (dcmff.getDataset()->findAndGetOFString(DCM_SOPClassUID, sopClassUid).good())
{
connection.AddStorageSOPClass(sopClassUid.c_str());
}
......@@ -482,7 +482,7 @@ namespace Orthanc
}
static void FixFindQuery(DicomMap& fixedQuery,
static void NormalizeFindQuery(DicomMap& fixedQuery,
ResourceType level,
const DicomMap& fields)
{
......@@ -590,11 +590,11 @@ namespace Orthanc
}
}
return new ParsedDicomFile(*fix);
return new ParsedDicomFile(*fix, GetDefaultDicomEncoding(), false /* be strict */);
}
default:
return new ParsedDicomFile(fields);
return new ParsedDicomFile(fields, GetDefaultDicomEncoding(), false /* be strict */);
}
}
......@@ -657,14 +657,26 @@ namespace Orthanc
void DicomUserConnection::Find(DicomFindAnswers& result,
ResourceType level,
const DicomMap& originalFields)
const DicomMap& originalFields,
bool normalize)
{
DicomMap fields;
FixFindQuery(fields, level, originalFields);
CheckIsOpen();
std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
std::auto_ptr<ParsedDicomFile> query;
if (normalize)
{
DicomMap fields;
NormalizeFindQuery(fields, level, originalFields);
query.reset(ConvertQueryFields(fields, manufacturer_));
}
else
{
query.reset(new ParsedDicomFile(originalFields,
GetDefaultDicomEncoding(),
false /* be strict */));
}
DcmDataset* dataset = query->GetDcmtkObject().getDataset();
const char* clevel = NULL;
......@@ -674,19 +686,19 @@ namespace Orthanc
{
case ResourceType_Patient:
clevel = "PATIENT";
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT");
sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
break;
case ResourceType_Study:
clevel = "STUDY";
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY");
sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
break;
case ResourceType_Series:
clevel = "SERIES";
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES");
sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
break;
......@@ -699,12 +711,12 @@ namespace Orthanc
// This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
// https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
// http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
clevel = "IMAGE";
}
else
{
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "INSTANCE");
}
sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
......@@ -731,37 +743,32 @@ namespace Orthanc
switch (level)
{
case ResourceType_Instance:
// SOP Instance UID
if (!fields.HasTag(0x0008, 0x0018))
if (!dataset->tagExists(DCM_SOPInstanceUID))
{
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0018), universal);
DU_putStringDOElement(dataset, DCM_SOPInstanceUID, universal);
}
case ResourceType_Series:
// Series instance UID
if (!fields.HasTag(0x0020, 0x000e))
if (!dataset->tagExists(DCM_SeriesInstanceUID))
{
DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000e), universal);
DU_putStringDOElement(dataset, DCM_SeriesInstanceUID, universal);
}
case ResourceType_Study:
// Accession number
if (!fields.HasTag(0x0008, 0x0050))
if (!dataset->tagExists(DCM_AccessionNumber))
{
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0050), universal);
DU_putStringDOElement(dataset, DCM_AccessionNumber, universal);
}
// Study instance UID
if (!fields.HasTag(0x0020, 0x000d))
if (!dataset->tagExists(DCM_StudyInstanceUID))
{
DU_putStringDOElement(dataset, DcmTagKey(0x0020, 0x000d), universal);
DU_putStringDOElement(dataset, DCM_StudyInstanceUID, universal);
}
case ResourceType_Patient:
// Patient ID
if (!fields.HasTag(0x0010, 0x0020))
if (!dataset->tagExists(DCM_PatientID))
{
DU_putStringDOElement(dataset, DcmTagKey(0x0010, 0x0020), universal);
DU_putStringDOElement(dataset, DCM_PatientID, universal);
}
break;
......@@ -789,15 +796,15 @@ namespace Orthanc
switch (level)
{
case ResourceType_Patient:
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "PATIENT");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT");
break;
case ResourceType_Study:
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "STUDY");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY");
break;
case ResourceType_Series:
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "SERIES");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES");
break;
case ResourceType_Instance:
......@@ -808,11 +815,11 @@ namespace Orthanc
// This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
// https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
// http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "IMAGE");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
}
else
{
DU_putStringDOElement(dataset, DcmTagKey(0x0008, 0x0052), "INSTANCE");
DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "INSTANCE");
}
break;
......@@ -1116,7 +1123,7 @@ namespace Orthanc
uint16_t moveOriginatorID)
{
if (buffer.size() > 0)
Store(reinterpret_cast<const char*>(&buffer[0]), buffer.size(), moveOriginatorAET, moveOriginatorID);
Store(&buffer[0], buffer.size(), moveOriginatorAET, moveOriginatorID);
else
Store(NULL, 0, moveOriginatorAET, moveOriginatorID);
}
......
......@@ -176,7 +176,8 @@ namespace Orthanc
void Find(DicomFindAnswers& result,
ResourceType level,
const DicomMap& fields);
const DicomMap& fields,
bool normalize); // Whether to normalize the DICOM query
void Move(const std::string& targetAet,
ResourceType level,
......
......@@ -94,6 +94,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "../../Logging.h"
#include <dcmtk/dcmnet/dcasccfg.h> /* for class DcmAssociationConfiguration */
#include <dcmtk/dcmdata/dcuid.h> /* for variable dcmAllStorageSOPClassUIDs */
#include <boost/lexical_cast.hpp>
static OFBool opt_rejectWithoutImplementationUID = OFFalse;
......@@ -217,7 +219,7 @@ static OFCondition acceptUnknownContextsWithTransferSyntax(
static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes(
T_ASC_Parameters * params,
const char* transferSyntaxes[], int transferSyntaxCount,
T_ASC_SC_ROLE acceptedRole = ASC_SC_ROLE_DEFAULT)
T_ASC_SC_ROLE acceptedRole)
{
OFCondition cond = EC_Normal;
/*
......@@ -239,159 +241,6 @@ namespace Orthanc
{
namespace Internals
{
/**
* EXTRACT OF FILE "dcmdata/libsrc/dcuid.cc" FROM DCMTK 3.6.0
* (dcmAllStorageSOPClassUIDs).
*
* an array of const strings containing all known Storage SOP
* Classes that fit into the conventional
* PATIENT-STUDY-SERIES-INSTANCE information model,
* i.e. everything a Storage SCP might want to store in a PACS.
* Special cases such as hanging protocol storage or the Storage
* SOP Class are not included in this list.
*
* THIS LIST CONTAINS ALL STORAGE SOP CLASSES INCLUDING RETIRED
* ONES AND IS LARGER THAN 64 ENTRIES.
*/
const char* orthancStorageSOPClassUIDs[] =
{
UID_AmbulatoryECGWaveformStorage,
UID_ArterialPulseWaveformStorage,
UID_AutorefractionMeasurementsStorage,
UID_BasicStructuredDisplayStorage,
UID_BasicTextSRStorage,
UID_BasicVoiceAudioWaveformStorage,
UID_BlendingSoftcopyPresentationStateStorage,
#if DCMTK_VERSION_NUMBER >= 361
UID_BreastProjectionXRayImageStorageForProcessing,
UID_BreastProjectionXRayImageStorageForPresentation,
#endif
UID_BreastTomosynthesisImageStorage,
UID_CardiacElectrophysiologyWaveformStorage,
UID_ChestCADSRStorage,
UID_ColonCADSRStorage,
UID_ColorSoftcopyPresentationStateStorage,
UID_ComprehensiveSRStorage,
UID_ComputedRadiographyImageStorage,
UID_CTImageStorage,
UID_DeformableSpatialRegistrationStorage,
UID_DigitalIntraOralXRayImageStorageForPresentation,
UID_DigitalIntraOralXRayImageStorageForProcessing,
UID_DigitalMammographyXRayImageStorageForPresentation,
UID_DigitalMammographyXRayImageStorageForProcessing,
UID_DigitalXRayImageStorageForPresentation,
UID_DigitalXRayImageStorageForProcessing,
UID_EncapsulatedCDAStorage,
UID_EncapsulatedPDFStorage,
UID_EnhancedCTImageStorage,
UID_EnhancedMRColorImageStorage,
UID_EnhancedMRImageStorage,
UID_EnhancedPETImageStorage,
UID_EnhancedSRStorage,
UID_EnhancedUSVolumeStorage,
UID_EnhancedXAImageStorage,
UID_EnhancedXRFImageStorage,
UID_GeneralAudioWaveformStorage,
UID_GeneralECGWaveformStorage,
UID_GenericImplantTemplateStorage,
UID_GrayscaleSoftcopyPresentationStateStorage,
UID_HemodynamicWaveformStorage,
UID_ImplantAssemblyTemplateStorage,
UID_ImplantationPlanSRDocumentStorage,
UID_ImplantTemplateGroupStorage,
UID_IntraocularLensCalculationsStorage,
UID_KeratometryMeasurementsStorage,
UID_KeyObjectSelectionDocumentStorage,
UID_LensometryMeasurementsStorage,
UID_MacularGridThicknessAndVolumeReportStorage,
UID_MammographyCADSRStorage,
UID_MRImageStorage,
UID_MRSpectroscopyStorage,
UID_MultiframeGrayscaleByteSecondaryCaptureImageStorage,
UID_MultiframeGrayscaleWordSecondaryCaptureImageStorage,
UID_MultiframeSingleBitSecondaryCaptureImageStorage,
UID_MultiframeTrueColorSecondaryCaptureImageStorage,
UID_NuclearMedicineImageStorage,
UID_OphthalmicAxialMeasurementsStorage,
UID_OphthalmicPhotography16BitImageStorage,
UID_OphthalmicPhotography8BitImageStorage,
UID_OphthalmicTomographyImageStorage,
UID_OphthalmicVisualFieldStaticPerimetryMeasurementsStorage,
UID_PositronEmissionTomographyImageStorage,
UID_ProcedureLogStorage,
UID_PseudoColorSoftcopyPresentationStateStorage,
UID_RawDataStorage,
UID_RealWorldValueMappingStorage,
UID_RespiratoryWaveformStorage,
UID_RTBeamsTreatmentRecordStorage,
UID_RTBrachyTreatmentRecordStorage,
UID_RTDoseStorage,
UID_RTImageStorage,
UID_RTIonBeamsTreatmentRecordStorage,
UID_RTIonPlanStorage,
UID_RTPlanStorage,
UID_RTStructureSetStorage,
UID_RTTreatmentSummaryRecordStorage,
UID_SecondaryCaptureImageStorage,
UID_SegmentationStorage,
UID_SpatialFiducialsStorage,
UID_SpatialRegistrationStorage,
UID_SpectaclePrescriptionReportStorage,
UID_StereometricRelationshipStorage,
UID_SubjectiveRefractionMeasurementsStorage,
UID_SurfaceSegmentationStorage,
UID_TwelveLeadECGWaveformStorage,
UID_UltrasoundImageStorage,
UID_UltrasoundMultiframeImageStorage,
UID_VideoEndoscopicImageStorage,
UID_VideoMicroscopicImageStorage,
UID_VideoPhotographicImageStorage,
UID_VisualAcuityMeasurementsStorage,
UID_VLEndoscopicImageStorage,
UID_VLMicroscopicImageStorage,
UID_VLPhotographicImageStorage,
UID_VLSlideCoordinatesMicroscopicImageStorage,
UID_VLWholeSlideMicroscopyImageStorage,
UID_XAXRFGrayscaleSoftcopyPresentationStateStorage,
UID_XRay3DAngiographicImageStorage,
UID_XRay3DCraniofacialImageStorage,
UID_XRayAngiographicImageStorage,
UID_XRayRadiationDoseSRStorage,
UID_XRayRadiofluoroscopicImageStorage,
// retired
UID_RETIRED_HardcopyColorImageStorage,
UID_RETIRED_HardcopyGrayscaleImageStorage,
UID_RETIRED_NuclearMedicineImageStorage,
UID_RETIRED_StandaloneCurveStorage,
UID_RETIRED_StandaloneModalityLUTStorage,
UID_RETIRED_StandaloneOverlayStorage,
UID_RETIRED_StandalonePETCurveStorage,
UID_RETIRED_StandaloneVOILUTStorage,
UID_RETIRED_StoredPrintStorage,
UID_RETIRED_UltrasoundImageStorage,
UID_RETIRED_UltrasoundMultiframeImageStorage,
UID_RETIRED_VLImageStorage,
#if DCMTK_VERSION_NUMBER >= 364
UID_RETIRED_VLMultiframeImageStorage,
#else
UID_RETIRED_VLMultiFrameImageStorage,
#endif
UID_RETIRED_XRayAngiographicBiPlaneImageStorage,
// draft
UID_DRAFT_SRAudioStorage,
UID_DRAFT_SRComprehensiveStorage,
UID_DRAFT_SRDetailStorage,
UID_DRAFT_SRTextStorage,
UID_DRAFT_WaveformStorage,
UID_DRAFT_RTBeamsDeliveryInstructionStorage,
NULL
};
const int orthancStorageSOPClassUIDsCount = (sizeof(orthancStorageSOPClassUIDs) / sizeof(const char*)) - 1;
OFCondition AssociationCleanup(T_ASC_Association *assoc)
{
OFCondition cond = ASC_dropSCPAssociation(assoc);
......@@ -588,7 +437,10 @@ namespace Orthanc
}
/* accept the Verification SOP Class if presented */
cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size());
cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
assoc->params,
&knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(),
&transferSyntaxes[0], transferSyntaxes.size());
if (cond.bad())
{
LOG(INFO) << cond.text();
......@@ -596,8 +448,24 @@ namespace Orthanc
return NULL;
}
/* the array of Storage SOP Class UIDs comes from dcuid.h */
cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, orthancStorageSOPClassUIDs, orthancStorageSOPClassUIDsCount, &transferSyntaxes[0], transferSyntaxes.size());
/* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */
size_t count = 0;
while (dcmAllStorageSOPClassUIDs[count] != NULL)
{
count++;
}
#if DCMTK_VERSION_NUMBER >= 362
// The global variable "numberOfDcmAllStorageSOPClassUIDs" is
// only published if DCMTK >= 3.6.2:
// https://bitbucket.org/sjodogne/orthanc/issues/137
assert(count == numberOfDcmAllStorageSOPClassUIDs);
#endif
cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
assoc->params,
dcmAllStorageSOPClassUIDs, count,
&transferSyntaxes[0], transferSyntaxes.size());
if (cond.bad())
{
LOG(INFO) << cond.text();
......@@ -613,7 +481,7 @@ namespace Orthanc
* to be a storage SOP class.
**/
cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
assoc->params, &transferSyntaxes[0], transferSyntaxes.size());
assoc->params, &transferSyntaxes[0], transferSyntaxes.size(), ASC_SC_ROLE_DEFAULT);
if (cond.bad())
{
LOG(INFO) << cond.text();
......
......@@ -158,6 +158,13 @@ namespace Orthanc
newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
return Action_Replace;
}
else if (parentTags.size() == 1 &&
parentTags[0] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE &&
tag == DICOM_TAG_SERIES_INSTANCE_UID)
{
newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series);
return Action_Replace;
}
else
{
return Action_None;
......
......@@ -521,15 +521,21 @@ namespace Orthanc
Json::Value& node = CreateNode(parentTags, parentIndexes, tag);
node[KEY_VR] = EnumerationToString(vr);
#if 0
/**
* TODO - The JSON file has an UTF-8 encoding, thus DCMTK
* replaces the specific character set with "ISO_IR 192"
* (UNICODE UTF-8). On Google Cloud Healthcare, however, the
* source encoding is reported, which seems more logical. We
* thus choose the Google convention. Enabling this block will
* mimic the DCMTK behavior.
**/
if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
{
// TODO - The JSON file has an UTF-8 encoding, thus DCMTK
// replaces the specific character set with "ISO_IR 192"
// (UNICODE UTF-8). It is unclear whether the source
// character set should be kept: We thus mimic DCMTK.
node[KEY_VALUE].append("ISO_IR 192");
}
else
#endif
{
std::string truncated;
......@@ -548,6 +554,15 @@ namespace Orthanc
std::vector<std::string> tokens;
Toolbox::TokenizeString(tokens, truncated, '\\');
if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET &&
tokens.size() > 1 &&
tokens[0].empty())
{
// Specific character set with code extension: Remove the
// first element from the vector of encodings
tokens.erase(tokens.begin());
}
node[KEY_VALUE] = Json::arrayValue;
for (size_t i = 0; i < tokens.size(); i++)
{
......@@ -584,29 +599,44 @@ namespace Orthanc
}
case ValueRepresentation_IntegerString:
if (tokens[i].empty())
{
/**
* The calls to "StripSpaces()" below fix the
* issue reported by Rana Asim Wajid on 2019-06-05
* ("Error Exception while invoking plugin service
* 32: Bad file format"):
* https://groups.google.com/d/msg/orthanc-users/T32FovWPcCE/-hKFbfRJBgAJ
**/
std::string t = Orthanc::Toolbox::StripSpaces(tokens[i]);
if (t.empty())
{
node[KEY_VALUE].append(Json::nullValue);
}
else
{
int64_t value = boost::lexical_cast<int64_t>(tokens[i]);
int64_t value = boost::lexical_cast<int64_t>(t);
node[KEY_VALUE].append(FormatInteger(value));
}
break;
}
case ValueRepresentation_DecimalString:
if (tokens[i].empty())
{
std::string t = Orthanc::Toolbox::StripSpaces(tokens[i]);
if (t.empty())
{
node[KEY_VALUE].append(Json::nullValue);
}
else
{
double value = boost::lexical_cast<double>(tokens[i]);
double value = boost::lexical_cast<double>(t);
node[KEY_VALUE].append(FormatDouble(value));
}
break;
}
default:
if (tokens[i].empty())
......
......@@ -2311,10 +2311,17 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
std::string utf8;
if (c != NULL) // This case corresponds to the empty string
{
if (element.getTag() == DCM_SpecificCharacterSet)
{
utf8.assign(c);
}
else
{
std::string s(c);
utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
}
}
std::string newValue;
ITagVisitor::Action action = visitor.VisitString
......
......@@ -771,9 +771,12 @@ namespace Orthanc
std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
if (!utf8Value.empty())
{
bool hasCodeExtensions;
Encoding encoding = DetectEncoding(hasCodeExtensions);
FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, encoding);
}
InsertInternal(dicom, element.release());
UpdateStorageUid(tag, utf8Value, false);
......@@ -912,12 +915,23 @@ namespace Orthanc
{
std::string patientId, studyUid, seriesUid, instanceUid;
if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
!GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID))
{
/**
* If "PatientID" is absent, be tolerant by considering it
* equals the empty string, then proceed. In Orthanc <= 1.5.6,
* an exception "Bad file format" was generated.
* https://groups.google.com/d/msg/orthanc-users/aphG_h1AHVg/rfOTtTPTAgAJ
* https://bitbucket.org/sjodogne/orthanc/commits/4c45e018bd3de3cfa21d6efc6734673aaaee4435
**/
patientId.clear();
}
if (!GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
!GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
!GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
{
throw OrthancException(ErrorCode_BadFileFormat);
throw OrthancException(ErrorCode_BadFileFormat, "missing StudyInstanceUID, SeriesInstanceUID or SOPInstanceUID");
}
return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
......@@ -1024,13 +1038,6 @@ namespace Orthanc
}
ParsedDicomFile::ParsedDicomFile(const DicomMap& map) :
pimpl_(new PImpl)
{
CreateFromDicomMap(map, GetDefaultDicomEncoding(), false /* be strict by default */);
}
ParsedDicomFile::ParsedDicomFile(const void* content,
size_t size) : pimpl_(new PImpl)
{
......
......@@ -101,8 +101,6 @@ namespace Orthanc
Encoding defaultEncoding,
bool permissive);
ParsedDicomFile(const DicomMap& map);
ParsedDicomFile(const void* content,
size_t size);
......
......@@ -57,6 +57,7 @@ namespace Orthanc
static const char* const MIME_SVG = "image/svg+xml";
static const char* const MIME_WEB_ASSEMBLY = "application/wasm";
static const char* const MIME_WOFF = "application/x-font-woff";
static const char* const MIME_WOFF2 = "font/woff2";
static const char* const MIME_XML_2 = "text/xml";
static const char* const MIME_ZIP = "application/zip";
static const char* const MIME_DICOM_WEB_JSON = "application/dicom+json";
......@@ -185,6 +186,9 @@ namespace Orthanc
case ErrorCode_CanceledJob:
return "This job was canceled";
case ErrorCode_BadGeometry:
return "Geometry error encountered in Stone";
case ErrorCode_SQLiteNotOpened:
return "SQLite: The database is not opened";
......@@ -362,6 +366,9 @@ namespace Orthanc
case ErrorCode_AlreadyExistingTag:
return "Cannot override the value of a tag that already exists";
case ErrorCode_UnsupportedMediaType:
return "Unsupported media type";
default:
if (error >= ErrorCode_START_PLUGINS)
{
......@@ -1115,6 +1122,9 @@ namespace Orthanc
case MimeType_Woff:
return MIME_WOFF;
case MimeType_Woff2:
return MIME_WOFF2;
case MimeType_PrometheusText:
// https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format
return "text/plain; version=0.0.4";
......@@ -1748,6 +1758,10 @@ namespace Orthanc
{
return MimeType_Woff;
}
else if (mime == MIME_WOFF2)
{
return MimeType_Woff2;
}
else if (mime == MIME_DICOM_WEB_JSON)
{
return MimeType_DicomWebJson;
......@@ -2127,6 +2141,9 @@ namespace Orthanc
case ErrorCode_CreateDicomParentIsInstance:
return HttpStatus_400_BadRequest;
case ErrorCode_UnsupportedMediaType:
return HttpStatus_415_UnsupportedMediaType;
default:
return HttpStatus_500_InternalServerError;
}
......
......@@ -49,17 +49,33 @@
// Macros "ORTHANC_OVERRIDE" and "ORTHANC_FINAL" wrap the "override"
// and "final" keywords introduced in C++11, to do compile-time
// checking of virtual methods
// The __cplusplus macro is broken in Visual Studio up to 15.6 and, in
// later versions, require the usage of the /Zc:__cplusplus flag
// We thus use an alternate way of checking for 'override' support
#ifdef ORTHANC_OVERRIDE_SUPPORTED
#error ORTHANC_OVERRIDE_SUPPORTED cannot be defined at this point
#endif
#if __cplusplus >= 201103L
// C++11 is enabled
# define ORTHANC_OVERRIDE_SUPPORTED 1
#else
# ifdef _MSC_VER
# if _MSC_VER >= 1600
# define ORTHANC_OVERRIDE_SUPPORTED 1
# endif
# endif
#endif
#if ORTHANC_OVERRIDE_SUPPORTED
// The override keyword (C++11) is enabled
# define ORTHANC_OVERRIDE override
# define ORTHANC_FINAL final
#else
// C++11 is disabled
// The override keyword (C++11) is not available
# define ORTHANC_OVERRIDE
# define ORTHANC_FINAL
#endif
namespace Orthanc
{
static const char* const URI_SCHEME_PREFIX_BINARY = "data:application/octet-stream;base64,";
......@@ -105,6 +121,7 @@ namespace Orthanc
MimeType_WebAssembly,
MimeType_Xml,
MimeType_Woff, // Web Open Font Format
MimeType_Woff2,
MimeType_Zip,
MimeType_PrometheusText, // Prometheus text-based exposition format (for metrics)
MimeType_DicomWebJson,
......@@ -162,6 +179,7 @@ namespace Orthanc
ErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */,
ErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */,
ErrorCode_CanceledJob = 37 /*!< This job was canceled */,
ErrorCode_BadGeometry = 38 /*!< Geometry error encountered in Stone */,
ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */,
ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */,
ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */,
......@@ -221,6 +239,7 @@ namespace Orthanc
ErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */,
ErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */,
ErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */,
ErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */,
ErrorCode_START_PLUGINS = 1000000
};
......
/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2019 Osimis S.A., Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* In addition, as a special exception, the copyright holders of this
* program give permission to link the code of its release with the
* OpenSSL project's "OpenSSL" library (or with modified versions of it
* that use the same license as the "OpenSSL" library), and distribute
* the linked executables. You must obey the GNU General Public License
* in all respects for all of the code used other than "OpenSSL". If you
* modify file(s) with this exception, you may extend this exception to
* your version of the file(s), but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files
* in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "PrecompiledHeaders.h"
#include "FileBuffer.h"
#include "TemporaryFile.h"
#include "OrthancException.h"
#include <boost/filesystem/fstream.hpp>
namespace Orthanc
{
class FileBuffer::PImpl
{
private:
TemporaryFile file_;
boost::filesystem::ofstream stream_;
bool isWriting_;
public:
PImpl() :
isWriting_(true)
{
stream_.open(file_.GetPath(), std::ofstream::out | std::ofstream::binary);
if (!stream_.good())
{
throw OrthancException(ErrorCode_CannotWriteFile);
}
}
~PImpl()
{
if (isWriting_)
{
stream_.close();
}
}
void Append(const char* buffer,
size_t size)
{
if (!isWriting_)
{
throw OrthancException(ErrorCode_BadSequenceOfCalls);
}
if (size > 0)
{
stream_.write(buffer, size);
if (!stream_.good())
{
stream_.close();
throw OrthancException(ErrorCode_FileStorageCannotWrite);
}
}
}
void Read(std::string& target)
{
if (isWriting_)
{
stream_.close();
isWriting_ = false;
}
file_.Read(target);
}
};
FileBuffer::FileBuffer() :
pimpl_(new PImpl)
{
}
void FileBuffer::Append(const char* buffer,
size_t size)
{
assert(pimpl_.get() != NULL);
pimpl_->Append(buffer, size);
}
void FileBuffer::Read(std::string& target)
{
assert(pimpl_.get() != NULL);
pimpl_->Read(target);
}
}
/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2019 Osimis S.A., Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* In addition, as a special exception, the copyright holders of this
* program give permission to link the code of its release with the
* OpenSSL project's "OpenSSL" library (or with modified versions of it
* that use the same license as the "OpenSSL" library), and distribute
* the linked executables. You must obey the GNU General Public License
* in all respects for all of the code used other than "OpenSSL". If you
* modify file(s) with this exception, you may extend this exception to
* your version of the file(s), but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files
* in the program, then also delete it here.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#pragma once
#if !defined(ORTHANC_SANDBOXED)
# error The macro ORTHANC_SANDBOXED must be defined
#endif
#if ORTHANC_SANDBOXED == 1
# error The namespace SystemToolbox cannot be used in sandboxed environments
#endif
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
namespace Orthanc
{
class FileBuffer : public boost::noncopyable
{
private:
class PImpl;
boost::shared_ptr<PImpl> pimpl_;
public:
FileBuffer();
void Append(const char* buffer,
size_t size);
void Read(std::string& target);
};
}