diff --git a/.gitignore b/.gitignore index 3c81f317e2..dbd12221d2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,7 @@ docker-compose/boutiques/boutiques shanoir-ng-preclinical/null* shanoir-uploader/src/main/resources/key shanoir-uploader/src/main/resources/profile.OFSEP/key -shanoir-uploader/src/main/resources/profile.OFSEP-qualif/key -shanoir-uploader/src/main/resources/profile.OFSEP-NG-qualif/key -shanoir-uploader/src/main/resources/profile.OFSEP-NG/key +shanoir-uploader/src/main/resources/profile.OFSEP-Qualif/key shanoir-uploader/src/main/resources/pseudonymus shanoir-uploader/src/test/resources/acr_phantom_t1/ shanoir-ng-tests/tests/geckodriver.log diff --git a/shanoir-ng-anonymization/src/main/resources/anonymization.xlsx b/shanoir-ng-anonymization/src/main/resources/anonymization.xlsx index 9b3181e3c8..0431e6aa03 100644 Binary files a/shanoir-ng-anonymization/src/main/resources/anonymization.xlsx and b/shanoir-ng-anonymization/src/main/resources/anonymization.xlsx differ diff --git a/shanoir-ng-back/pom.xml b/shanoir-ng-back/pom.xml index 2c05c1ed24..cb2bba7be8 100644 --- a/shanoir-ng-back/pom.xml +++ b/shanoir-ng-back/pom.xml @@ -49,7 +49,7 @@ along with this program. If not, see https://www.gnu.org/licenses/gpl-3.0.html <java.version>17</java.version> <mapstruct.version>1.5.3.Final</mapstruct.version> <keycloak.version>22.0.1</keycloak.version> - <dcm4che.version>5.30.0</dcm4che.version> + <dcm4che.version>5.31.0</dcm4che.version> </properties> <dependencyManagement> diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/datasetacquisition/model/mr/ContrastAgentUsed.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/datasetacquisition/model/mr/ContrastAgentUsed.java index 99f00d5abb..9b84efff50 100644 --- a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/datasetacquisition/model/mr/ContrastAgentUsed.java +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/datasetacquisition/model/mr/ContrastAgentUsed.java @@ -41,7 +41,10 @@ public enum ContrastAgentUsed { GADOVIST(7), // Clariscan - CLARISCAN(8); + CLARISCAN(8), + + // Dotarem + DOTAREM(9); private int id; diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/dto/Serie.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/dto/Serie.java index 86b6159ae4..8447d4c985 100644 --- a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/dto/Serie.java +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/dto/Serie.java @@ -245,10 +245,11 @@ public void setSequenceName(String sequenceName) { public DatasetFile getFirstDatasetFileForCurrentSerie() { if (getDatasets() == null - || getDatasets().get(0) == null - || getDatasets().get(0).getExpressionFormats() == null - || getDatasets().get(0).getExpressionFormats().get(0) == null - || getDatasets().get(0).getExpressionFormats().get(0).getDatasetFiles() == null) { + || getDatasets().get(0) == null + || getDatasets().get(0).getExpressionFormats() == null + || getDatasets().get(0).getExpressionFormats().get(0) == null + || getDatasets().get(0).getExpressionFormats().get(0).getDatasetFiles() == null + || getDatasets().get(0).getExpressionFormats().get(0).getDatasetFiles().get(0) == null) { return null; } return getDatasets().get(0).getExpressionFormats().get(0).getDatasetFiles().get(0); diff --git a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/service/ImporterService.java b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/service/ImporterService.java index 93db909824..2f38a45451 100644 --- a/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/service/ImporterService.java +++ b/shanoir-ng-datasets/src/main/java/org/shanoir/ng/importer/service/ImporterService.java @@ -221,7 +221,7 @@ private Set<DatasetAcquisition> generateAcquisitions(Examination examination, Im try { dicomAttributes = dicomProcessing.getDicomObjectAttributes(serie.getFirstDatasetFileForCurrentSerie(), serie.getIsEnhanced()); } catch (IOException e) { - throw new ShanoirException("Unable to retrieve dicom attributes in file " + serie.getFirstDatasetFileForCurrentSerie().getPath(), e); + throw new ShanoirException("Unable to retrieve dicom attributes in serie: " + serie.getSeriesDescription(), e); } // Generate acquisition object with all sub objects : datasets, protocols, expressions, ... diff --git a/shanoir-ng-front/src/app/import/query-pacs/query-pacs.component.html b/shanoir-ng-front/src/app/import/query-pacs/query-pacs.component.html index b0bc8e6ec0..0470f0d26e 100644 --- a/shanoir-ng-front/src/app/import/query-pacs/query-pacs.component.html +++ b/shanoir-ng-front/src/app/import/query-pacs/query-pacs.component.html @@ -14,7 +14,7 @@ <form [formGroup]="form" novalidate> - <div class="header command-zone">1. Query Neurinfo PACS</div> + <div class="header command-zone">1. Query PACS</div> <fieldset class="step"> <ol> <legend> @@ -69,4 +69,4 @@ </ol> </fieldset> <button class="next" [disabled]="!form.valid" (click)="queryPACS()">Query</button> -</form> \ No newline at end of file +</form> diff --git a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterApiController.java b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterApiController.java index 01585a3d2f..2747cbfd2f 100644 --- a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterApiController.java +++ b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterApiController.java @@ -29,7 +29,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.regex.Matcher; @@ -75,8 +74,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ByteArrayResource; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; @@ -87,7 +84,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; -import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import com.fasterxml.jackson.databind.ObjectMapper; @@ -145,9 +141,6 @@ public class ImporterApiController implements ImporterApi { @Value("${shanoir.import.directory}") private String importDir; - @Autowired - private RestTemplate restTemplate; - @Autowired private DicomDirGeneratorService dicomDirGeneratorService; @@ -268,7 +261,6 @@ public ResponseEntity<Void> startImportJob( final File importJobDir = new File(userImportDir, tempDirId); if (importJobDir.exists()) { importJob.setWorkFolder(importJobDir.getAbsolutePath()); - cleanSeries(importJob); LOG.info("Starting import job for user {} (userId: {}) with import job folder: {}", KeycloakUtil.getTokenUserName(), userId, importJob.getWorkFolder()); importerManagerService.manageImportJob(importJob); return new ResponseEntity<>(HttpStatus.OK); @@ -279,24 +271,6 @@ public ResponseEntity<Void> startImportJob( } } - private void cleanSeries(final ImportJob importJob) { - for (Iterator<Patient> patientIt = importJob.getPatients().iterator(); patientIt.hasNext();) { - Patient patient = patientIt.next(); - List<Study> studies = patient.getStudies(); - for (Iterator<Study> studyIt = studies.iterator(); studyIt.hasNext();) { - Study study = studyIt.next(); - List<Serie> series = study.getSeries(); - for (Iterator<Serie> serieIt = series.iterator(); serieIt.hasNext();) { - Serie serie = serieIt.next(); - if (serie.isIgnored() || serie.isErroneous() || !serie.getSelected()) { - LOG.info("Serie {} cleaned from import (ignored, erroneous, not selected).", serie.getSeriesDescription()); - serieIt.remove(); - } - } - } - } - } - @Override public ResponseEntity<ImportJob> queryPACS( @Parameter(name = "DicomQuery", required = true) @Valid @RequestBody final DicomQuery dicomQuery) @@ -309,7 +283,6 @@ public ResponseEntity<ImportJob> queryPACS( importJob.setWorkFolder(""); importJob.setFromPacs(true); importJob.setUserId(KeycloakUtil.getTokenUserId()); - } catch (ShanoirException e) { throw new RestServiceException( new ErrorModel(HttpStatus.UNPROCESSABLE_ENTITY.value(), e.getMessage(), null)); @@ -332,12 +305,11 @@ public ResponseEntity<ImportJob> importDicomZipFile( MockMultipartFile multiPartFile; try { multiPartFile = new MockMultipartFile(tempFile.getName(), tempFile.getName(), APPLICATION_ZIP, new FileInputStream(tempFile.getAbsolutePath())); - // Import dicomfile return uploadDicomZipFile(multiPartFile); } catch (IOException e) { LOG.error("ERROR while loading zip fiole, please contact an administrator"); - e.printStackTrace(); + LOG.error(e.getMessage(), e); return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); } finally { // Delete temp file which is useless now @@ -374,12 +346,9 @@ public ResponseEntity<EegImportJob> uploadEEGZipFile( if (!userImportDir.exists()) { userImportDir.mkdirs(); // create if not yet existing } - // Unzip the file and get the elements File tempFile = ImportUtils.saveTempFile(userImportDir, eegFile); - File importJobDir = ImportUtils.saveTempFileCreateFolderAndUnzip(tempFile, eegFile, false); - EegImportJob importJob = new EegImportJob(); importJob.setUserId(userId); importJob.setArchive(eegFile.getOriginalFilename()); diff --git a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterManagerService.java b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterManagerService.java index 9d5a0c3310..60f8dc02cc 100644 --- a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterManagerService.java +++ b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/ImporterManagerService.java @@ -142,35 +142,18 @@ public void manageImportJob(final ImportJob importJob) { } else { throw new ShanoirException("Unsupported type of import."); } + // we do the clean series here: at this point we are sure for all imports, that the ImagesCreatorAndDicomFileAnalyzer + // has been run and correctly classified everything. So no need to check afterwards for erroneous series. + cleanSeries(importJob); event.setProgress(0.25F); eventService.publishEvent(event); for (Iterator<Patient> patientsIt = patients.iterator(); patientsIt.hasNext();) { Patient patient = patientsIt.next(); - // perform anonymization only in case of profile explicitly set - if (importJob.getAnonymisationProfileToUse() == null || !importJob.getAnonymisationProfileToUse().isEmpty()) { - String anonymizationProfile = (String) this.rabbitTemplate.convertSendAndReceive(RabbitMQConfiguration.STUDY_ANONYMISATION_PROFILE_QUEUE, importJob.getStudyId()); - importJob.setAnonymisationProfileToUse(anonymizationProfile); - } - ArrayList<File> dicomFiles = getDicomFilesForPatient(importJob, patient, importJobDir.getAbsolutePath()); - final Subject subject = patient.getSubject(); - - if (subject == null) { - LOG.error("Error: subject == null in importJob."); - throw new ShanoirException("Error: subject == null in importJob."); - } - - final String subjectName = subject.getName(); - - event.setMessage("Pseudonymizing DICOM files for subject [" + subjectName + "]..."); - eventService.publishEvent(event); - - try { - ANONYMIZER.anonymizeForShanoir(dicomFiles, importJob.getAnonymisationProfileToUse(), subjectName, subjectName); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - throw new ShanoirException("Error during pseudonymization."); + // DICOM files coming from ShUp are already pseudonymized + if (!importJob.isFromShanoirUploader()) { + pseudonymize(importJob, event, importJobDir, patient); } Long converterId = importJob.getConverterId(); datasetsCreatorAndNIfTIConverter.createDatasetsAndRunConversion(patient, importJobDir, converterId, importJob); @@ -188,6 +171,54 @@ public void manageImportJob(final ImportJob importJob) { } } + /** + * cleanSeries is important here for import-from-zip file: when the ImagesCreatorAndDicomFileAnalyzer + * has declared some series as e.g. erroneous, we have to remove them from the import. For import-from + * pacs or from-sh-up it is different, as the ImagesCreatorAndDicomFileAnalyzer is called afterwards. + * Same here for multi-exam-imports: it calls uploadDicomZipFile method, where series could be classed + * as erroneous and when startImportJob is called, we want them to be removed from the import. + * + * @param importJob + */ + private void cleanSeries(final ImportJob importJob) { + for (Iterator<Patient> patientIt = importJob.getPatients().iterator(); patientIt.hasNext();) { + Patient patient = patientIt.next(); + List<Study> studies = patient.getStudies(); + for (Iterator<Study> studyIt = studies.iterator(); studyIt.hasNext();) { + Study study = studyIt.next(); + List<Serie> series = study.getSeries(); + for (Iterator<Serie> serieIt = series.iterator(); serieIt.hasNext();) { + Serie serie = serieIt.next(); + if (serie.isIgnored() || serie.isErroneous() || !serie.getSelected()) { + LOG.info("Serie {} cleaned from import (ignored, erroneous, not selected).", serie.getSeriesDescription()); + serieIt.remove(); + } + } + } + } + } + + private void pseudonymize(final ImportJob importJob, ShanoirEvent event, final File importJobDir, Patient patient) + throws FileNotFoundException, ShanoirException { + if (importJob.getAnonymisationProfileToUse() == null || !importJob.getAnonymisationProfileToUse().isEmpty()) { + ArrayList<File> dicomFiles = getDicomFilesForPatient(importJob, patient, importJobDir.getAbsolutePath()); + final Subject subject = patient.getSubject(); + if (subject == null) { + LOG.error("Error: subject == null in importJob."); + throw new ShanoirException("Error: subject == null in importJob."); + } + final String subjectName = subject.getName(); + event.setMessage("Pseudonymizing DICOM files for subject [" + subjectName + "]..."); + eventService.publishEvent(event); + try { + ANONYMIZER.anonymizeForShanoir(dicomFiles, importJob.getAnonymisationProfileToUse(), subjectName, subjectName); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + throw new ShanoirException("Error during pseudonymization."); + } + } + } + private void sendFailureMail(ImportJob importJob, String errorMessage) { EmailDatasetImportFailed generatedMail = new EmailDatasetImportFailed(); generatedMail.setExaminationId(importJob.getExaminationId().toString()); @@ -195,9 +226,7 @@ private void sendFailureMail(ImportJob importJob, String errorMessage) { generatedMail.setSubjectName(importJob.getSubjectName()); generatedMail.setStudyName(importJob.getStudyName()); generatedMail.setUserId(importJob.getUserId()); - generatedMail.setErrorMessage(errorMessage != null ? errorMessage : "An unexpected error occured, please contact Shanoir support."); - sendMail(importJob, generatedMail); } @@ -208,7 +237,6 @@ private void sendFailureMail(ImportJob importJob, String errorMessage) { */ private void sendMail(ImportJob job, EmailBase email) { List<Long> recipients = new ArrayList<>(); - // Get all recpients List<StudyUser> users = (List<StudyUser>) studyUserRightRepo.findByStudyId(job.getStudyId()); for (StudyUser user : users) { @@ -221,7 +249,6 @@ private void sendMail(ImportJob job, EmailBase email) { return; } email.setRecipients(recipients); - try { rabbitTemplate.convertAndSend(RabbitMQConfiguration.IMPORT_DATASET_FAILED_MAIL_QUEUE, objectMapper.writeValueAsString(email)); } catch (AmqpException | JsonProcessingException e) { @@ -310,8 +337,8 @@ private void downloadAndMoveDicomFilesToImportJobDir(final File importJobDir, Li /** * Using Java HashSet here to avoid duplicate files for Pseudonymization. - * For performance reasons already init with 5000 buckets, assuming, - * that we will normally never have more than 5000 files to process. + * For performance reasons already init with 10000 buckets, assuming, + * that we will normally never have more than 10000 files to process. * Maybe to be evaluated later with more bigger imports. * * @param importJob @@ -321,7 +348,7 @@ private void downloadAndMoveDicomFilesToImportJobDir(final File importJobDir, Li * @throws FileNotFoundException */ private ArrayList<File> getDicomFilesForPatient(final ImportJob importJob, final Patient patient, final String workFolderPath) throws FileNotFoundException { - Set<File> pathsSet = new HashSet<>(5000); + Set<File> pathsSet = new HashSet<>(10000); List<Study> studies = patient.getStudies(); for (Iterator<Study> studiesIt = studies.iterator(); studiesIt.hasNext();) { Study study = studiesIt.next(); diff --git a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dcm2nii/DatasetsCreatorAndNIfTIConverterService.java b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dcm2nii/DatasetsCreatorAndNIfTIConverterService.java index 2c8940a472..ad436371a6 100644 --- a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dcm2nii/DatasetsCreatorAndNIfTIConverterService.java +++ b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dcm2nii/DatasetsCreatorAndNIfTIConverterService.java @@ -36,13 +36,10 @@ import java.util.Set; import java.util.stream.Collectors; -import jakarta.transaction.Transactional; - import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.RegexFileFilter; -import org.apache.poi.ss.formula.eval.NotImplementedException; import org.shanoir.ng.importer.model.Dataset; import org.shanoir.ng.importer.model.DatasetFile; import org.shanoir.ng.importer.model.DiffusionGradient; @@ -69,6 +66,8 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; +import jakarta.transaction.Transactional; + /** * The NIfTIConverter does the actual conversion of dcm to nii files. * To use the converter the dcm files have to be put in separate folders. @@ -149,36 +148,35 @@ public void createDatasetsAndRunConversion(Patient patient, File workFolder, Lon Study study = studiesIt.next(); List<Serie> series = study.getSelectedSeries(); float progress = 0; - int nbSeries = series.size(); int cpt = 1; - for (Iterator<Serie> seriesIt = series.iterator(); seriesIt.hasNext();) { Serie serie = seriesIt.next(); - - progress = progress + (0.5f / series.size()); - importJob.getShanoirEvent().setProgress(progress); - importJob.getShanoirEvent().setMessage("Converting to NIfTI for serie [" + (serie.getProtocolName() == null ? serie.getSeriesInstanceUID() : serie.getProtocolName()) + "] (" + cpt + "/" + nbSeries + ")..."); - shanoirEventService.publishEvent(importJob.getShanoirEvent()); - - File serieIDFolderFile = createSerieIDFolderAndMoveFiles(workFolder, seriesFolderFile, serie); - boolean serieIdentifiedForNotSeparating; - try { - serieIdentifiedForNotSeparating = checkSerieForPropertiesString(serie, seriesProperties); - // if the serie is not one of the series, that should not be separated, please separate the series, - // otherwise just do not separate the series and keep all images for one nii conversion - serie.setDatasets(new ArrayList<Dataset>()); - constructDicom(serieIDFolderFile, serie, serieIdentifiedForNotSeparating); - // we exclude MR Spectroscopy (MRS) from NIfTI conversion, see MRS on GitHub Wiki - if (serie.getIsSpectroscopy() != null && !serie.getIsSpectroscopy()) { - constructNifti(serieIDFolderFile, serie, converterId); + // do not convert an erroneous serie + if (!serie.isErroneous() && !serie.isIgnored()) { + progress = progress + (0.5f / series.size()); + importJob.getShanoirEvent().setProgress(progress); + importJob.getShanoirEvent().setMessage("Converting to NIfTI for serie [" + (serie.getProtocolName() == null ? serie.getSeriesInstanceUID() : serie.getProtocolName()) + "] (" + cpt + "/" + nbSeries + ")..."); + shanoirEventService.publishEvent(importJob.getShanoirEvent()); + File serieIDFolderFile = createSerieIDFolderAndMoveFiles(workFolder, seriesFolderFile, serie); + boolean serieIdentifiedForNotSeparating; + try { + serieIdentifiedForNotSeparating = checkSerieForPropertiesString(serie, seriesProperties); + // if the serie is not one of the series, that should not be separated, please separate the series, + // otherwise just do not separate the series and keep all images for one nii conversion + serie.setDatasets(new ArrayList<Dataset>()); + constructDicom(serieIDFolderFile, serie, serieIdentifiedForNotSeparating); + // we exclude MR Spectroscopy (MRS) from NIfTI conversion, see MRS on GitHub Wiki + if (serie.getIsSpectroscopy() != null && !serie.getIsSpectroscopy()) { + constructNifti(serieIDFolderFile, serie, converterId); + } + } catch (NoSuchFieldException | SecurityException e) { + LOG.error(e.getMessage()); } - } catch (NoSuchFieldException | SecurityException e) { - LOG.error(e.getMessage()); + // as images/non-images are migrated to datasets, clear the list now + serie.getImages().clear(); + serie.getNonImages().clear(); } - // as images/non-images are migrated to datasets, clear the list now - serie.getImages().clear(); - serie.getNonImages().clear(); cpt++; } } diff --git a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/ImagesCreatorAndDicomFileAnalyzerService.java b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/ImagesCreatorAndDicomFileAnalyzerService.java index 14cda143d7..0549c1be25 100644 --- a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/ImagesCreatorAndDicomFileAnalyzerService.java +++ b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/ImagesCreatorAndDicomFileAnalyzerService.java @@ -157,7 +157,7 @@ private void filterAndCreateImages(String folderFileAbsolutePath, Serie serie, b for (Iterator<Instance> instancesIt = instances.iterator(); instancesIt.hasNext();) { Instance instance = instancesIt.next(); File instanceFile = getFileFromInstance(instance, serie, folderFileAbsolutePath, isImportFromPACS); - processOneDicomFileForAllInstances(instanceFile, images, folderFileAbsolutePath); + processDicomFilePerInstanceAndCreateImage(instanceFile, images, folderFileAbsolutePath); } serie.setNonImages(nonImages); serie.setNonImagesNumber(nonImages.size()); @@ -221,14 +221,13 @@ public File getFileFromInstance(Instance instance, Serie serie, String folderFil * @param nonImages * @param images */ - private void processOneDicomFileForAllInstances(File dicomFile, List<Image> images, String folderFileAbsolutePath) throws Exception { + private void processDicomFilePerInstanceAndCreateImage(File dicomFile, List<Image> images, String folderFileAbsolutePath) throws Exception { try (DicomInputStream dIS = new DicomInputStream(dicomFile)) { // keep try to finally close input stream Attributes attributes = dIS.readDataset(); // Some DICOM files with a particular SOPClassUID are ignored: such as Raw Data Storage etc. if (DicomSerieAndInstanceAnalyzer.checkInstanceIsIgnored(attributes)) { // do nothing here as instances list will be emptied after split between images and non-images } else { - // divide here between non-images and images, non-images at first Image image = new Image(); /** * Attention: the path of each image is always relative: either to the temporary folder created @@ -242,7 +241,7 @@ private void processOneDicomFileForAllInstances(File dicomFile, List<Image> imag } catch (IOException iOE) { throw iOE; } catch (Exception e) { - LOG.error("Error while processing DICOM file: " + dicomFile.getAbsolutePath()); + LOG.error("Error while processing DICOM file, one for entire serie: " + dicomFile.getAbsolutePath()); throw e; } } diff --git a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/query/QueryPACSService.java b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/query/QueryPACSService.java index e04a4a8ea5..d81580eff1 100644 --- a/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/query/QueryPACSService.java +++ b/shanoir-ng-import/src/main/java/org/shanoir/ng/importer/dicom/query/QueryPACSService.java @@ -26,19 +26,22 @@ import org.apache.commons.lang3.StringUtils; import org.dcm4che3.data.Attributes; +import org.dcm4che3.data.ElementDictionary; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; +import org.dcm4che3.data.VR; import org.dcm4che3.net.ApplicationEntity; import org.dcm4che3.net.Association; import org.dcm4che3.net.Connection; import org.dcm4che3.net.Device; -import org.dcm4che3.net.DimseRSP; +import org.dcm4che3.net.DimseRSPHandler; import org.dcm4che3.net.IncompatibleConnectionException; +import org.dcm4che3.net.Priority; import org.dcm4che3.net.QueryOption; +import org.dcm4che3.net.Status; import org.dcm4che3.net.pdu.AAssociateRQ; import org.dcm4che3.net.pdu.PresentationContext; import org.dcm4che3.net.service.QueryRetrieveLevel; -import org.dcm4che3.tool.findscu.FindSCU.InformationModel; import org.shanoir.ng.importer.dicom.DicomSerieAndInstanceAnalyzer; import org.shanoir.ng.importer.dicom.InstanceNumberSorter; import org.shanoir.ng.importer.dicom.SeriesNumberSorter; @@ -52,7 +55,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.weasis.dicom.op.CFind; import org.weasis.dicom.op.CMove; import org.weasis.dicom.param.AdvancedParams; import org.weasis.dicom.param.DicomNode; @@ -93,26 +95,74 @@ public class QueryPACSService { private DicomNode called; + private Association association; + @Value("${shanoir.import.pacs.store.aet.called.name}") private String calledNameSCP; public QueryPACSService() {} // for ShUp usage + /** + * Used within microservice MS Import on the server, via PostConstruct. + */ @PostConstruct private void initDicomNodes() { // Initialize connection configuration parameters here: to be used for all queries - this.calling = new DicomNode(callingName, callingHost, callingPort); // ShUp - this.called = new DicomNode(calledName, calledHost, calledPort); // PACS + this.calling = new DicomNode(callingName, callingHost, callingPort); + this.called = new DicomNode(calledName, calledHost, calledPort); + LOG.info("Query: DicomNodes initialized via CDI: calling ({}, {}, {}) and called ({}, {}, {})", + callingName, callingHost, callingPort, calledName, calledHost, calledPort); } + /** + * Do configuration of QueryPACSService from outside. Used by ShanoirUploader. + * + * @param calling + * @param called + * @param calledNameSCP + */ public void setDicomNodes(DicomNode calling, DicomNode called, String calledNameSCP) { this.calling = calling; this.called = called; this.calledNameSCP = calledNameSCP; this.maxPatientsFromPACS = 10; + LOG.info("Query: DicomNodes initialized via method call (ShUp): calling ({}, {}, {}) and called ({}, {}, {})", + calling.getAet(), calling.getHostname(), calling.getPort(), called.getAet(), called.getHostname(), called.getPort()); + } + + private void connectAssociation(DicomNode calling, DicomNode called) { + ExecutorService executor = Executors.newSingleThreadExecutor(); + ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); + try { + // calling: create a device, a connection and an application entity + Device device = new Device(this.getClass().getName()); + device.setExecutor(executor); + device.setScheduledExecutor(scheduledExecutor); + ApplicationEntity callingAE = new ApplicationEntity(calling.getAet()); + Connection callingConn = new Connection(); + device.addConnection(callingConn); + device.addApplicationEntity(callingAE); + callingAE.addConnection(callingConn); + // called: create a connection and an AAssociateRQ + Connection calledConn = new Connection(null, called.getHostname(), called.getPort()); + AAssociateRQ aarq = new AAssociateRQ(); + aarq.setCallingAET(calling.getAet()); + aarq.setCalledAET(called.getAet()); + aarq.addPresentationContext(new PresentationContext(1, + UID.Verification, UID.ImplicitVRLittleEndian)); + aarq.addPresentationContext(new PresentationContext(2, + UID.PatientRootQueryRetrieveInformationModelFind, UID.ImplicitVRLittleEndian)); + aarq.addPresentationContext(new PresentationContext(3, + UID.StudyRootQueryRetrieveInformationModelFind, UID.ImplicitVRLittleEndian)); + this.association = callingAE.connect(calledConn, aarq); + LOG.info("connectAssociation finished between calling {} and called {}", calling.getAet(), called.getAet()); + } catch (IOException | InterruptedException | IncompatibleConnectionException | GeneralSecurityException e) { + LOG.error(e.getMessage(), e); + } } public ImportJob queryCFIND(DicomQuery dicomQuery) throws ShanoirImportException { + connectAssociation(calling, called); ImportJob importJob = new ImportJob(); /** * In case of any patient specific search field is filled, work on patient level. Highest priority. @@ -122,7 +172,7 @@ public ImportJob queryCFIND(DicomQuery dicomQuery) throws ShanoirImportException || StringUtils.isNotBlank(dicomQuery.getPatientBirthDate())) { // For Patient Name and Patient ID, wild card search is not allowed if (!dicomQuery.getPatientName().contains("*") && !dicomQuery.getPatientID().contains("*")) { - queryPatientLevel(dicomQuery, calling, called, importJob); + queryPatientLevel(dicomQuery, importJob); } // @Todo: implement wild card search // Do Fuzzy search on base of patient name here @@ -136,13 +186,26 @@ public ImportJob queryCFIND(DicomQuery dicomQuery) throws ShanoirImportException */ } else if (StringUtils.isNotBlank(dicomQuery.getStudyDescription()) || StringUtils.isNotBlank(dicomQuery.getStudyDate())) { - queryStudyLevel(dicomQuery, calling, called, importJob); + queryStudyLevel(dicomQuery, importJob); } else { throw new ShanoirImportException("DicomQuery: missing parameters."); } + releaseAssociation(); return importJob; } + private void releaseAssociation() { + try { + this.association.release(); + } catch (IOException e) { + LOG.error(e.getMessage(), e); + } + ExecutorService executorService = (ExecutorService) this.association.getDevice().getExecutor(); + executorService.shutdown(); + this.association.getDevice().getScheduledExecutor().shutdown(); + LOG.info("releaseAssociation finished between calling {} and called {}", calling.getAet(), called.getAet()); + } + public void queryCMOVE(Serie serie) { queryCMOVE(serie.getSeriesInstanceUID()); } @@ -163,61 +226,32 @@ public void handleProgression(DicomProgress progress) { } public boolean queryECHO(String calledAET, String hostName, int port, String callingAET) { + connectAssociation(calling, called); LOG.info("DICOM ECHO: Starting with configuration {}, {}, {} <- {}", calledAET, hostName, port, callingAET); - ExecutorService executor = Executors.newSingleThreadExecutor(); - ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); try { - // set up calling configuration - Device device = new Device("c-echo-scu"); - ApplicationEntity callingAE = new ApplicationEntity(callingAET); - Connection callingConn = new Connection(); - device.addApplicationEntity(callingAE); - device.addConnection(callingConn); - device.setExecutor(executor); - device.setScheduledExecutor(scheduledExecutor); - callingAE.addConnection(callingConn); - - Connection calledConn = new Connection(null, hostName, port); - AAssociateRQ aarq = new AAssociateRQ(); - aarq.setCallingAET(callingAET); - aarq.setCalledAET(calledAET); - aarq.addPresentationContext(new PresentationContext(1, - UID.Verification, UID.ImplicitVRLittleEndian)); - Association as = callingAE.connect(calledConn, aarq); - as.cecho(); - as.release(); + this.association.cecho(); } catch (IOException e) { LOG.error(e.getMessage(), e); return false; } catch (InterruptedException e) { LOG.error(e.getMessage(), e); return false; - } catch (IncompatibleConnectionException e) { - LOG.error(e.getMessage(), e); - return false; - } catch (GeneralSecurityException e) { - LOG.error(e.getMessage(), e); - return false; - } finally { - executor.shutdown(); - scheduledExecutor.shutdown(); - } + } + releaseAssociation(); return true; } /** * This method queries on patient root level. * @param dicomQuery - * @param calling - * @param called * @param importJob */ - private void queryPatientLevel(DicomQuery dicomQuery, DicomNode calling, DicomNode called, ImportJob importJob) { + private void queryPatientLevel(DicomQuery dicomQuery, ImportJob importJob) { DicomParam patientName = initDicomParam(Tag.PatientName, dicomQuery.getPatientName()); DicomParam patientID = initDicomParam(Tag.PatientID, dicomQuery.getPatientID()); DicomParam patientBirthDate = initDicomParam(Tag.PatientBirthDate, dicomQuery.getPatientBirthDate()); DicomParam[] params = { patientName, patientID, patientBirthDate, new DicomParam(Tag.PatientBirthName), new DicomParam(Tag.PatientSex) }; - List<Attributes> attributesPatients = queryCFIND(params, QueryRetrieveLevel.PATIENT, calling, called); + List<Attributes> attributesPatients = queryCFind(params, QueryRetrieveLevel.PATIENT); if (attributesPatients != null) { // Limit the max number of patients returned int patientsNbre = attributesPatients.size(); @@ -230,7 +264,7 @@ private void queryPatientLevel(DicomQuery dicomQuery, DicomNode calling, DicomNo boolean patientExists = patients.stream().anyMatch(p -> p.getPatientID().equals(patient.getPatientID())); if (!patientExists) { patients.add(patient); - queryStudies(calling, called, dicomQuery, patient); + queryStudies(dicomQuery, patient); } } importJob.setPatients(patients); @@ -244,13 +278,13 @@ private void queryPatientLevel(DicomQuery dicomQuery, DicomNode calling, DicomNo * @param called * @param importJob */ - private void queryStudyLevel(DicomQuery dicomQuery, DicomNode calling, DicomNode called, ImportJob importJob) { + private void queryStudyLevel(DicomQuery dicomQuery, ImportJob importJob) { DicomParam studyDescription = initDicomParam(Tag.StudyDescription, dicomQuery.getStudyDescription()); DicomParam studyDate = initDicomParam(Tag.StudyDate, dicomQuery.getStudyDate()); DicomParam[] params = { studyDescription, studyDate, new DicomParam(Tag.PatientName), new DicomParam(Tag.PatientID), new DicomParam(Tag.PatientBirthDate), new DicomParam(Tag.PatientBirthName), new DicomParam(Tag.PatientSex), new DicomParam(Tag.StudyInstanceUID) }; - List<Attributes> attributesStudies = queryCFIND(params, QueryRetrieveLevel.STUDY, calling, called); + List<Attributes> attributesStudies = queryCFind(params, QueryRetrieveLevel.STUDY); if (attributesStudies != null) { List<Patient> patients = new ArrayList<>(); for (int i = 0; i < attributesStudies.size(); i++) { @@ -260,7 +294,7 @@ private void queryStudyLevel(DicomQuery dicomQuery, DicomNode calling, DicomNode // handle studies Study study = new Study(attributesStudies.get(i)); patient.getStudies().add(study); - querySeries(calling, called, study); + querySeries(study); } // Limit the max number of patients returned if (maxPatientsFromPACS < patients.size()) { @@ -310,7 +344,7 @@ private DicomParam initDicomParam(int tag, String value) { * @param dicomQuery * @param patient */ - private void queryStudies(DicomNode calling, DicomNode called, DicomQuery dicomQuery, Patient patient) { + private void queryStudies(DicomQuery dicomQuery, Patient patient) { DicomParam[] params = { new DicomParam(Tag.PatientID, patient.getPatientID()), new DicomParam(Tag.PatientName, patient.getPatientName()), @@ -318,13 +352,13 @@ private void queryStudies(DicomNode calling, DicomNode called, DicomQuery dicomQ new DicomParam(Tag.StudyDate, dicomQuery.getStudyDate()), new DicomParam(Tag.StudyDescription, dicomQuery.getStudyDescription()) }; - List<Attributes> attributesStudies = queryCFIND(params, QueryRetrieveLevel.STUDY, calling, called); + List<Attributes> attributesStudies = queryCFind(params, QueryRetrieveLevel.STUDY); if (attributesStudies != null) { List<Study> studies = new ArrayList<>(); for (int i = 0; i < attributesStudies.size(); i++) { Study study = new Study(attributesStudies.get(i)); studies.add(study); - querySeries(calling, called, study); + querySeries(study); } studies.sort((p1, p2) -> p1.getStudyDate().compareTo(p2.getStudyDate())); patient.setStudies(studies); @@ -337,7 +371,7 @@ private void queryStudies(DicomNode calling, DicomNode called, DicomQuery dicomQ * @param called * @param study */ - private void querySeries(DicomNode calling, DicomNode called, Study study) { + private void querySeries(Study study) { DicomParam[] params = { new DicomParam(Tag.StudyInstanceUID, study.getStudyInstanceUID()), new DicomParam(Tag.SeriesInstanceUID), @@ -351,14 +385,14 @@ private void querySeries(DicomNode calling, DicomNode called, Study study) { new DicomParam(Tag.ManufacturerModelName), new DicomParam(Tag.DeviceSerialNumber) }; - List<Attributes> attributesList = queryCFIND(params, QueryRetrieveLevel.SERIES, calling, called); + List<Attributes> attributesList = queryCFind(params, QueryRetrieveLevel.SERIES); if (attributesList != null) { List<Serie> series = new ArrayList<Serie>(); for (int i = 0; i < attributesList.size(); i++) { Attributes attributes = attributesList.get(i); Serie serie = new Serie(attributes); if (!DicomSerieAndInstanceAnalyzer.checkSerieIsIgnored(attributes)) { - queryInstances(calling, called, serie, study); + queryInstances(serie, study); if (!serie.getInstances().isEmpty()) { DicomSerieAndInstanceAnalyzer.checkSerieIsEnhanced(serie, attributes); DicomSerieAndInstanceAnalyzer.checkSerieIsSpectroscopy(serie); @@ -386,14 +420,14 @@ private void querySeries(DicomNode calling, DicomNode called, Study study) { * @param called * @param serie */ - private void queryInstances(DicomNode calling, DicomNode called, Serie serie, Study study) { + private void queryInstances(Serie serie, Study study) { DicomParam[] params = { new DicomParam(Tag.StudyInstanceUID, study.getStudyInstanceUID()), new DicomParam(Tag.SeriesInstanceUID, serie.getSeriesInstanceUID()), new DicomParam(Tag.SOPInstanceUID), new DicomParam(Tag.InstanceNumber) }; - List<Attributes> attributes = queryCFIND(params, QueryRetrieveLevel.IMAGE, calling, called); + List<Attributes> attributes = queryCFind(params, QueryRetrieveLevel.IMAGE); if (attributes != null) { List<Instance> instances = new ArrayList<>(); for (int i = 0; i < attributes.size(); i++) { @@ -409,38 +443,75 @@ private void queryInstances(DicomNode calling, DicomNode called, Serie serie, St /** * This method does a C-FIND query and returns the results. - * @param params + * + * The state of each c-find query is a local attribute of the method. + * So, when e.g. on the server 3 users call in parallel queryCFind, + * each one has its own DimseRSPHandler and its own state, so this + * might work, in case the association is not caching aspects. + * + * @param keys * @param level - * @param calling - * @param called * @return */ - private List<Attributes> queryCFIND(DicomParam[] params, QueryRetrieveLevel level, final DicomNode calling, final DicomNode called) { - AdvancedParams options = new AdvancedParams(); + private List<Attributes> queryCFind(DicomParam[] keys, QueryRetrieveLevel level) { + String cuid = null; if (level.equals(QueryRetrieveLevel.PATIENT)) { - options.setInformationModel(InformationModel.PatientRoot); + cuid = UID.PatientRootQueryRetrieveInformationModelFind; } else if (level.equals(QueryRetrieveLevel.STUDY)) { - options.setInformationModel(InformationModel.StudyRoot); + cuid = UID.StudyRootQueryRetrieveInformationModelFind; } else if (level.equals(QueryRetrieveLevel.SERIES)) { - options.setInformationModel(InformationModel.StudyRoot); + cuid = UID.StudyRootQueryRetrieveInformationModelFind; } else if (level.equals(QueryRetrieveLevel.IMAGE)) { - options.setInformationModel(InformationModel.StudyRoot); + cuid = UID.StudyRootQueryRetrieveInformationModelFind; } - logQuery(params, options); - DicomState state = CFind.process(options, calling, called, 0, level, params); - return state.getDicomRSP(); - } - - /** - * This method logs the params and options of the PACS query. - * @param params - * @param options - */ - private void logQuery(DicomParam[] params, AdvancedParams options) { - LOG.info("Calling PACS, C-FIND with level: {} and params:", options.getInformationModel()); - for (int i = 0; i < params.length; i++) { - LOG.info("Tag: {}, Value: {}", params[i].getTagName(), Arrays.toString(params[i].getValues())); + DicomState state = new DicomState(new DicomProgress()); + DimseRSPHandler rspHandler = new DimseRSPHandler(this.association.nextMessageID()) { + @Override + public void onDimseRSP(Association as, Attributes cmd, Attributes data) { + super.onDimseRSP(as, cmd, data); + int status = cmd.getInt(Tag.Status, -1); + if (Status.isPending(status)) { + state.addDicomRSP(data); + } else { + state.setStatus(status); + } + } + }; + try { + Attributes attributes = new Attributes(); + attributes.setString(Tag.QueryRetrieveLevel, VR.CS, level.name()); + for (DicomParam p : keys) { + addAttributes(attributes, p); + } + LOG.info("Calling PACS, C-FIND with level: {}", level); + for (int i = 0; i < keys.length; i++) { + LOG.info("Tag: {}, Value: {}", keys[i].getTagName(), Arrays.toString(keys[i].getValues())); + } + association.cfind(cuid, Priority.NORMAL, attributes, null, rspHandler); + if (association.isReadyForDataTransfer()) { + association.waitForOutstandingRSP(); + } + } catch (IOException | InterruptedException e) { + LOG.error("Error in c-find query: ", e); } + return state.getDicomRSP(); } + + private void addAttributes(Attributes attrs, DicomParam param) { + int tag = param.getTag(); + String[] ss = param.getValues(); + VR vr = ElementDictionary.vrOf(tag, attrs.getPrivateCreator(tag)); + if (ss == null || ss.length == 0) { + // Returning key + if (vr == VR.SQ) { + attrs.newSequence(tag, 1).add(new Attributes(0)); + } else { + attrs.setNull(tag, vr); + } + } else { + // Matching key + attrs.setString(tag, vr, ss); + } + } } diff --git a/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApi.java b/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApi.java index 1108d70eac..7f603fa96b 100644 --- a/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApi.java +++ b/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApi.java @@ -30,7 +30,6 @@ import org.shanoir.ng.study.dua.DataUserAgreement; import org.shanoir.ng.study.model.Study; import org.shanoir.ng.study.model.StudyUser; -import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PreAuthorize; diff --git a/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApiController.java b/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApiController.java index d93bd905d9..3ae3c7f341 100644 --- a/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApiController.java +++ b/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/controler/StudyApiController.java @@ -21,16 +21,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.text.SimpleDateFormat; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Date; +import java.util.Arrays; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; -import org.joda.time.format.DateTimeFormat; import org.shanoir.ng.shared.core.model.IdName; import org.shanoir.ng.shared.error.FieldErrorMap; import org.shanoir.ng.shared.event.ShanoirEvent; @@ -210,6 +206,7 @@ public ResponseEntity<StudyDTO> saveNewStudy(@RequestBody final Study study, fin Study createdStudy; try { + addCurrentUserAsStudyUserIfEmptyStudyUsers(study); createdStudy = studyService.create(study); eventService.publishEvent(new ShanoirEvent(ShanoirEventType.CREATE_STUDY_EVENT, createdStudy.getId().toString(), KeycloakUtil.getTokenUserId(), "", ShanoirEvent.SUCCESS)); @@ -220,6 +217,19 @@ public ResponseEntity<StudyDTO> saveNewStudy(@RequestBody final Study study, fin return new ResponseEntity<>(studyMapper.studyToStudyDTO(createdStudy), HttpStatus.OK); } + private void addCurrentUserAsStudyUserIfEmptyStudyUsers(final Study study) { + if (study.getStudyUserList() == null) { + List<StudyUser> studyUserList = new ArrayList<StudyUser>(); + StudyUser studyUser = new StudyUser(); + studyUser.setStudy(study); + studyUser.setUserId(KeycloakUtil.getTokenUserId()); + studyUser.setUserName(KeycloakUtil.getTokenUserName()); + studyUser.setStudyUserRights(Arrays.asList(StudyUserRight.CAN_SEE_ALL, StudyUserRight.CAN_DOWNLOAD, StudyUserRight.CAN_IMPORT, StudyUserRight.CAN_ADMINISTRATE)); + studyUserList.add(studyUser); + study.setStudyUserList(studyUserList); + } + } + @Override public ResponseEntity<StudyStorageVolumeDTO> getDetailedStorageVolume(@PathVariable("studyId") final Long studyId) throws RestServiceException { StudyStorageVolumeDTO dto = studyService.getDetailedStorageVolume(studyId); diff --git a/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/service/StudyServiceImpl.java b/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/service/StudyServiceImpl.java index 66bc237cb3..d959b3be04 100644 --- a/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/service/StudyServiceImpl.java +++ b/shanoir-ng-studies/src/main/java/org/shanoir/ng/study/service/StudyServiceImpl.java @@ -118,7 +118,6 @@ public class StudyServiceImpl implements StudyService { @Autowired private SubjectStudyRepository subjectStudyRepository; - @Override public void deleteById(final Long id) throws EntityNotFoundException { final Study study = studyRepository.findById(id).orElse(null); @@ -154,8 +153,10 @@ public Study create(final Study study) throws MicroServiceCommunicationException } } - for (SubjectStudy subjectStudy : study.getSubjectStudyList()) { - subjectStudy.setStudy(study); + if (study.getSubjectStudyList() != null) { + for (SubjectStudy subjectStudy : study.getSubjectStudyList()) { + subjectStudy.setStudy(study); + } } if (study.getTags() != null) { @@ -185,15 +186,16 @@ public Study create(final Study study) throws MicroServiceCommunicationException } } - List<SubjectStudy> subjectStudyListSave = new ArrayList<SubjectStudy>(study.getSubjectStudyList()); + List<SubjectStudy> subjectStudyListSave = null; + if (study.getSubjectStudyList() != null) { + subjectStudyListSave = new ArrayList<SubjectStudy>(study.getSubjectStudyList()); + } Map<Long, List<SubjectStudyTag>> subjectStudyTagSave = new HashMap<>(); study.setSubjectStudyList(null); Study studyDb = studyRepository.save(study); - //studyDb.setSubjectStudyList(new ArrayList<SubjectStudy>()); if (subjectStudyListSave != null) { updateTags(subjectStudyListSave, studyDb.getTags()); - //ListDependencyUpdate.updateWith(studyDb.getSubjectStudyList(), subjectStudyListSave); studyDb.setSubjectStudyList(new ArrayList<>()); for (SubjectStudy subjectStudy : subjectStudyListSave) { SubjectStudy newSubjectStudy = new SubjectStudy(); @@ -203,7 +205,6 @@ public Study create(final Study study) throws MicroServiceCommunicationException newSubjectStudy.setSubjectType(subjectStudy.getSubjectType()); newSubjectStudy.setStudy(studyDb); subjectStudyTagSave.put(subjectStudy.getSubject().getId(), subjectStudy.getSubjectStudyTags()); - //newSubjectStudy.setSubjectStudyTags(subjectStudy.getSubjectStudyTags()); studyDb.getSubjectStudyList().add(newSubjectStudy); } studyDb = studyRepository.save(studyDb); diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/ShUpConfig.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/ShUpConfig.java index e3860623da..e5d1379fbd 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/ShUpConfig.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/ShUpConfig.java @@ -70,6 +70,8 @@ public class ShUpConfig { public static final String RANDOM_SEED = "random.seed"; + public static final String PROFILE = "profile"; + /** * Static variables */ diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/DownloadOrCopyRunnable.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/DownloadOrCopyRunnable.java index a10c38d08f..a632c4a784 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/DownloadOrCopyRunnable.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/DownloadOrCopyRunnable.java @@ -66,12 +66,12 @@ public void run() { /** * 2. Fill MRI information into serie from first DICOM file of each serie - * This has already been done for CD/DVD import, but not yet here for PACS + * This has already been done for CD/DVD import, but not yet here for PACS. */ if (this.isFromPACS) { for (Iterator iterator = selectedSeries.iterator(); iterator.hasNext();) { SerieTreeNode serieTreeNode = (SerieTreeNode) iterator.next(); - dicomFileAnalyzer.getAdditionalMetaDataFromFirstInstanceOfSerie(filePathDicomDir, serieTreeNode.getSerie(), null, isFromPACS); + dicomFileAnalyzer.getAdditionalMetaDataFromFirstInstanceOfSerie(uploadFolder.getAbsolutePath(), serieTreeNode.getSerie(), null, isFromPACS); } } } catch (FileNotFoundException e) { diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/FindDicomActionListener.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/FindDicomActionListener.java index 918ff875a5..037699401c 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/FindDicomActionListener.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/FindDicomActionListener.java @@ -246,7 +246,7 @@ private void fillMediaWithPatients(Media media, final List<Patient> patients) { for (Iterator iterator = patients.iterator(); iterator.hasNext();) { Patient patient = (Patient) iterator.next(); final PatientTreeNode patientTreeNode = media.initChildTreeNode(patient); - logger.info("Patient info read from DICOMDIR: " + patient.toString()); + logger.info("Patient info read: " + patient.toString()); // add patients media.addTreeNode(patient.getPatientID(), patientTreeNode); List<Study> studies = patient.getStudies(); diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/ImportFinishRunnable.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/ImportFinishRunnable.java index 307310fe49..f3ceaab523 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/ImportFinishRunnable.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/ImportFinishRunnable.java @@ -60,13 +60,12 @@ public void run() { boolean anonymizationSuccess = false; try { String anonymizationProfile = ShUpConfig.profileProperties.getProperty(ANONYMIZATION_PROFILE); - anonymizationSuccess = anonymizer.anonymize(uploadFolder, anonymizationProfile, subjectName); + anonymizationSuccess = anonymizer.pseudonymize(uploadFolder, anonymizationProfile, subjectName); } catch (IOException e) { logger.error(uploadFolder.getName() + ": " + e.getMessage(), e); } if (anonymizationSuccess) { - logger.info(uploadFolder.getName() + ": DICOM files successfully anonymized."); /** * Write import-job.json to disk */ diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/InitialStartupState.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/InitialStartupState.java index 772e3644e3..c0cab11ef6 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/InitialStartupState.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/InitialStartupState.java @@ -49,7 +49,9 @@ public class InitialStartupState implements State { private static final String SU_V6_0_3 = ".su_v6.0.3"; private static final String SU_V6_0_4 = ".su_v6.0.4"; - + + private static final String SU_V7_0_1 = ".su_v7.0.1"; + public void load(StartupStateContext context) throws Exception { initShanoirUploaderFolder(); initLogging(); @@ -60,8 +62,8 @@ public void load(StartupStateContext context) throws Exception { logger.info(System.getProperty("java.vendor.url")); logger.info(System.getProperty("java.version")); InetAddress inetAddress = InetAddress.getLocalHost(); - logger.info("IP Address:- " + inetAddress.getHostAddress()); - logger.info("Host Name:- " + inetAddress.getHostName()); + logger.info("IP Address: " + inetAddress.getHostAddress()); + logger.info("Host Name: " + inetAddress.getHostName()); // Disable http request to check for quartz upload System.setProperty("org.quartz.scheduler.skipUpdateCheck", "true"); System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); @@ -70,6 +72,7 @@ public void load(StartupStateContext context) throws Exception { initLanguage(); copyPseudonymus(); initProfiles(); + initProfile(); initStartupDialog(context); context.setState(new ProxyConfigurationState()); context.nextState(); @@ -78,7 +81,8 @@ public void load(StartupStateContext context) throws Exception { private void doMigration() throws IOException { // as properties, that exist already are not replaced/changed, start with the last version before, // as considered as more important - // overwrite with properties from ShanoirUploader v6.0.4 or v6.0.3, if existing + // overwrite with properties from ShanoirUploader v7.0.1, v6.0.4 or v6.0.3, if existing + migrateFromVersion(SU_V7_0_1); migrateFromVersion(SU_V6_0_4); migrateFromVersion(SU_V6_0_3); // migrate properties from ShanoirUploader v5.2 @@ -91,11 +95,11 @@ private void migrateFromVersion(String version) throws IOException { final File shanoirUploaderFolderForVersion = new File(shanoirUploaderFolderPathForVersion); boolean shanoirUploaderFolderExistsForVersion = shanoirUploaderFolderForVersion.exists(); if (shanoirUploaderFolderExistsForVersion) { - logger.info("Start migrating properties from version " + version + " (.su == v5.2) of ShUp."); + logger.info("Start migrating properties from version " + version + " of ShUp."); copyPropertiesFile(shanoirUploaderFolderForVersion, ShUpConfig.shanoirUploaderFolder, ShUpConfig.LANGUAGE_PROPERTIES); copyPropertiesFile(shanoirUploaderFolderForVersion, ShUpConfig.shanoirUploaderFolder, ShUpConfig.PROXY_PROPERTIES); copyPropertiesFile(shanoirUploaderFolderForVersion, ShUpConfig.shanoirUploaderFolder, ShUpConfig.DICOM_SERVER_PROPERTIES); - logger.info("Finished migrating properties from version " + version + " (.su == v5.2) of ShUp: language, proxy, dicom_server."); + logger.info("Finished migrating properties from version " + version + " of ShUp: language, proxy, dicom_server."); } } @@ -297,4 +301,10 @@ private void initLanguage() { } } + private void initProfile() throws FileNotFoundException, IOException { + String profile = ShUpConfig.basicProperties.getProperty(ShUpConfig.PROFILE); + if (profile != null && !profile.isEmpty()) { + ShUpConfig.profileSelected = profile; + } + } } diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/ProxyConfigurationState.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/ProxyConfigurationState.java index 6a9266d888..1bd5a2e6bc 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/ProxyConfigurationState.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/ProxyConfigurationState.java @@ -37,7 +37,7 @@ public void load(StartupStateContext context) { switch (httpResponseCode){ case 200 : context.getShUpStartupDialog().updateStartupText("\n" + ShUpConfig.resourceBundle.getString("shanoir.uploader.startup.test.proxy.success")); - context.setState(new SelectProfileManualConfigurationState()); + context.setState(new SelectProfileConfigurationState()); context.nextState(); break; default: diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/SelectProfileConfigurationState.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/SelectProfileConfigurationState.java new file mode 100644 index 0000000000..4bc75a3a7c --- /dev/null +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/SelectProfileConfigurationState.java @@ -0,0 +1,25 @@ +package org.shanoir.uploader.action.init; + + +import org.apache.log4j.Logger; +import org.shanoir.uploader.ShUpConfig; + +public class SelectProfileConfigurationState implements State { + + private static Logger logger = Logger.getLogger(SelectProfileConfigurationState.class); + + public void load(StartupStateContext context) { + if(ShUpConfig.profileSelected == null) { + context.setState(new SelectProfileManualConfigurationState()); + context.nextState(); + } else { + logger.info("Profile found in basic.properties. Used as default: " + ShUpConfig.profileSelected); + SelectProfilePanelActionListener actionListener = new SelectProfilePanelActionListener(null, null); + actionListener.configureSelectedProfile(ShUpConfig.profileSelected); + context.getShUpStartupDialog().updateStartupText("\nProfile: " + ShUpConfig.profileSelected); + context.setState(new AuthenticationConfigurationState()); + context.nextState(); + } + } + +} diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/SelectProfilePanelActionListener.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/SelectProfilePanelActionListener.java index 12deea5f8f..6d410c95c4 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/SelectProfilePanelActionListener.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/action/init/SelectProfilePanelActionListener.java @@ -30,14 +30,16 @@ public SelectProfilePanelActionListener(SelectProfileConfigurationPanel selectPr public void actionPerformed(ActionEvent e) { String selectedProfile = (String) selectProfilePanel.selectProfileCB.getSelectedItem(); ShUpConfig.profileSelected = selectedProfile; + configureSelectedProfile(selectedProfile); + sSC.nextState(); + } + + public void configureSelectedProfile(String selectedProfile) { String filePath = File.separator + ShUpConfig.PROFILE_DIR + selectedProfile; ShUpConfig.profileDirectory = new File(ShUpConfig.shanoirUploaderFolder, filePath); logger.info("Profile directory set to: " + ShUpConfig.profileDirectory.getAbsolutePath()); File profilePropertiesFile = new File(ShUpConfig.profileDirectory, ShUpConfig.PROFILE_PROPERTIES); loadPropertiesFromFile(profilePropertiesFile, ShUpConfig.profileProperties); - - ShUpConfig.encryption.decryptIfEncryptedString(profilePropertiesFile, - ShUpConfig.profileProperties, "shanoir.server.user.password"); logger.info("Profile " + selectedProfile + " successfully initialized."); File keycloakJson = new File(ShUpConfig.profileDirectory, ShUpConfig.KEYCLOAK_JSON); @@ -47,7 +49,7 @@ public void actionPerformed(ActionEvent e) { } else { logger.error("Error: missing keycloak.json! Connection with sh-ng will not work."); return; - } + } // check if pseudonymus has been copied in case of true if (Boolean.parseBoolean(ShUpConfig.profileProperties.getProperty(ShUpConfig.MODE_PSEUDONYMUS))) { @@ -72,7 +74,6 @@ public void actionPerformed(ActionEvent e) { return; } } - sSC.nextState(); } private void loadPropertiesFromFile(final File propertiesFile, final Properties properties) { diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/DicomServerClient.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/DicomServerClient.java index a38f4bb1a7..c4a9556b1f 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/DicomServerClient.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/DicomServerClient.java @@ -42,6 +42,7 @@ public class DicomServerClient implements IDicomServerClient { private File workFolder; public DicomServerClient(final Properties dicomServerProperties, final File workFolder) { + logger.info("New DicomServerClient created with properties: " + dicomServerProperties.toString()); config.initWithPropertiesFile(dicomServerProperties); this.workFolder = workFolder; // Initialize connection configuration parameters here: to be used for all queries @@ -126,17 +127,25 @@ public boolean accept(File dir, String name) { } } }; - File[] newFileNames = uploadFolder.listFiles(oldFileNamesAndDICOMFilter); - logger.debug("newFileNames: " + newFileNames.length); - for (int i = 0; i < newFileNames.length; i++) { - fileNamesForSerie.add(newFileNames[i].getName()); + File serieFolder = new File (uploadFolder.getAbsolutePath() + File.separator + seriesInstanceUID); + if (serieFolder.exists()) { + File[] newFileNames = serieFolder.listFiles(oldFileNamesAndDICOMFilter); + logger.debug("newFileNames: " + newFileNames.length); + for (int i = 0; i < newFileNames.length; i++) { + fileNamesForSerie.add(newFileNames[i].getName()); + } + serieTreeNode.setFileNames(fileNamesForSerie); + retrievedDicomFiles.addAll(fileNamesForSerie); + oldFileNames.addAll(fileNamesForSerie); + logger.info(uploadFolder.getName() + ":\n\n Download of " + fileNamesForSerie.size() + + " DICOM files for serie " + seriesInstanceUID + ": " + serieTreeNode.getDisplayString() + + " was successful.\n\n"); + } else { + logger.error(uploadFolder.getName() + ":\n\n Download of " + fileNamesForSerie.size() + + " DICOM files for serie " + seriesInstanceUID + ": " + serieTreeNode.getDisplayString() + + " has failed.\n\n"); + return null; } - serieTreeNode.setFileNames(fileNamesForSerie); - retrievedDicomFiles.addAll(fileNamesForSerie); - oldFileNames.addAll(fileNamesForSerie); - logger.info(uploadFolder.getName() + ":\n\n Download of " + fileNamesForSerie.size() - + " DICOM files for serie " + seriesInstanceUID + ": " + serieTreeNode.getDisplayString() - + " was successful.\n\n"); } else { logger.error(uploadFolder.getName() + ":\n\n Download of " + fileNamesForSerie.size() + " DICOM files for serie " + seriesInstanceUID + ": " + serieTreeNode.getDisplayString() diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/anonymize/Anonymizer.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/anonymize/Anonymizer.java index 838c5326eb..59c7e4ab8c 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/anonymize/Anonymizer.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/anonymize/Anonymizer.java @@ -7,35 +7,39 @@ import org.apache.log4j.Logger; import org.shanoir.anonymization.anonymization.AnonymizationService; import org.shanoir.anonymization.anonymization.AnonymizationServiceImpl; +import org.shanoir.uploader.dicom.retrieve.DcmRcvManager; public class Anonymizer { private static Logger logger = Logger.getLogger(Anonymizer.class); - public boolean anonymize(final File uploadFolder, + public boolean pseudonymize(final File uploadFolder, final String profile, final String subjectName) throws IOException { - ArrayList<File> dicomFiles = getListOfDicomFiles(uploadFolder); + ArrayList<File> dicomFiles = new ArrayList<File>(); + getListOfDicomFiles(uploadFolder, dicomFiles); try { AnonymizationService anonymizationService = new AnonymizationServiceImpl(); anonymizationService.anonymizeForShanoir(dicomFiles, profile, subjectName, subjectName); + logger.info("--> " + dicomFiles.size() + " DICOM files successfully pseudonymized."); } catch (Exception e) { - logger.error("anonymization service: ", e); + logger.error("pseudonymization service: ", e); return false; } return true; } - private ArrayList<File> getListOfDicomFiles(final File uploadFolder) - throws IOException { - ArrayList<File> dicomFileList = new ArrayList<File>(); - File[] listOfFiles = uploadFolder.listFiles(); + private void getListOfDicomFiles(final File folder, ArrayList<File> dicomFiles) throws IOException { + File[] listOfFiles = folder.listFiles(); for (File file : listOfFiles) { - if (file.isFile() && !file.getName().endsWith(".xml")) { - dicomFileList.add(file); + if (file.isFile() && file.getName().endsWith(DcmRcvManager.DICOM_FILE_SUFFIX)) { + dicomFiles.add(file); + } else { + if (file.isDirectory()) { + getListOfDicomFiles(file, dicomFiles); + } } } - return dicomFileList; } } diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/query/SerieTreeNode.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/query/SerieTreeNode.java index 56022c4621..6d421585a4 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/query/SerieTreeNode.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/query/SerieTreeNode.java @@ -44,6 +44,8 @@ public class SerieTreeNode implements DicomTreeNode { private List<String> fileNames; + private MRI mriInformation; + // constructor for JAXB public SerieTreeNode() { this.serie = new Serie(); @@ -274,21 +276,27 @@ public DicomTreeNode initChildTreeNode(Object arg0) { @XmlElement public MRI getMriInformation() { - MRI mriInformation = new MRI(); - InstitutionDicom institutionDicom = this.serie.getInstitution(); - if(institutionDicom != null) { - mriInformation.setInstitutionName(institutionDicom.getInstitutionName()); - mriInformation.setInstitutionAddress(institutionDicom.getInstitutionAddress()); - } - EquipmentDicom equipmentDicom = this.serie.getEquipment(); - if(equipmentDicom != null) { - mriInformation.setManufacturer(equipmentDicom.getManufacturer()); - mriInformation.setManufacturersModelName(equipmentDicom.getManufacturerModelName()); - mriInformation.setDeviceSerialNumber(equipmentDicom.getDeviceSerialNumber()); - mriInformation.setStationName(equipmentDicom.getStationName()); - mriInformation.setMagneticFieldStrength(equipmentDicom.getMagneticFieldStrength()); + if (this.mriInformation == null) { + this.mriInformation = new MRI(); + InstitutionDicom institutionDicom = this.serie.getInstitution(); + if(institutionDicom != null) { + this.mriInformation.setInstitutionName(institutionDicom.getInstitutionName()); + this.mriInformation.setInstitutionAddress(institutionDicom.getInstitutionAddress()); + } + EquipmentDicom equipmentDicom = this.serie.getEquipment(); + if(equipmentDicom != null) { + this.mriInformation.setManufacturer(equipmentDicom.getManufacturer()); + this.mriInformation.setManufacturersModelName(equipmentDicom.getManufacturerModelName()); + this.mriInformation.setDeviceSerialNumber(equipmentDicom.getDeviceSerialNumber()); + this.mriInformation.setStationName(equipmentDicom.getStationName()); + this.mriInformation.setMagneticFieldStrength(equipmentDicom.getMagneticFieldStrength()); + } } - return mriInformation; + return this.mriInformation; + } + + public void setMriInformation(MRI mriInformation) { + this.mriInformation = mriInformation; } @Override diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/retrieve/DcmRcvManager.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/retrieve/DcmRcvManager.java index 4c442fc7c9..872ec89abc 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/retrieve/DcmRcvManager.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/dicom/retrieve/DcmRcvManager.java @@ -12,8 +12,6 @@ /** * The DcmRcvHelper handles the download of DICOM files. - * It's a local service / server which is started, when - * the ShanoirUploader is started. * * @author mkain * @@ -26,7 +24,7 @@ public class DcmRcvManager { * In the brackets '{ggggeeee}' the dicom attribute value is used to be replaced. * We store in a folder with the SeriesInstanceUID and the file name of the SOPInstanceUID. */ - private static final String STORAGE_PATTERN = "{00080018}"; + private static final String STORAGE_PATTERN = "{0020000E}" + File.separator + "{00080018}"; public static final String DICOM_FILE_SUFFIX = ".dcm"; @@ -53,6 +51,11 @@ public void configure(final ConfigBean configBean) { this.lParams = new ListenerParams(params, true, STORAGE_PATTERN + DICOM_FILE_SUFFIX, null, null); } + /** + * Called from a synchronized method only, so should not be a problem for multiple usages. + * + * @param folderPath + */ public void startSCPServer(final String folderPath) { try { if(this.listener != null) diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/gui/MainWindow.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/gui/MainWindow.java index 7f4ac0c97c..2b8f3901d4 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/gui/MainWindow.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/gui/MainWindow.java @@ -549,6 +549,7 @@ public void actionPerformed(ActionEvent e) { gbc_queryButton.gridy = 6; queryPanel.add(queryButton, gbc_queryButton); queryButton.setEnabled(false); + frame.getRootPane().setDefaultButton(queryButton); queryButton.addActionListener(fAL); JSeparator separator = new JSeparator(); diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/model/rest/Study.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/model/rest/Study.java index 4ccc002abd..f877b8d3c4 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/model/rest/Study.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/model/rest/Study.java @@ -13,10 +13,11 @@ public class Study implements Comparable<Study> { private String name; + private String studyStatus; + private List<StudyCard> studyCards; - @JsonProperty("studyCenterList") - private List<Center> centers; + private List<StudyCenter> studyCenterList; private Boolean compatible; @@ -36,6 +37,14 @@ public void setName(String name) { this.name = name; } + public String getStudyStatus() { + return studyStatus; + } + + public void setStudyStatus(String studyStatus) { + this.studyStatus = studyStatus; + } + public List<StudyCard> getStudyCards() { return studyCards; } @@ -44,13 +53,12 @@ public void setStudyCards(List<StudyCard> studyCards) { this.studyCards = studyCards; } - public List<Center> getCenters() { - return centers; + public List<StudyCenter> getStudyCenterList() { + return studyCenterList; } - public void setCenters(List<Center> centers) { - this.centers = centers; - Collections.sort(this.centers); + public void setStudyCenterList(List<StudyCenter> studyCenterList) { + this.studyCenterList = studyCenterList; } public Boolean getCompatible() { diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/model/rest/StudyCenter.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/model/rest/StudyCenter.java new file mode 100644 index 0000000000..ab2f6348b7 --- /dev/null +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/model/rest/StudyCenter.java @@ -0,0 +1,25 @@ +package org.shanoir.uploader.model.rest; + +public class StudyCenter { + + private Center center; + + private Study study; + + public Center getCenter() { + return center; + } + + public void setCenter(Center center) { + this.center = center; + } + + public Study getStudy() { + return study; + } + + public void setStudy(Study study) { + this.study = study; + } + +} diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/service/rest/ShanoirUploaderServiceClient.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/service/rest/ShanoirUploaderServiceClient.java index 152f524794..c4f8ef2987 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/service/rest/ShanoirUploaderServiceClient.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/service/rest/ShanoirUploaderServiceClient.java @@ -51,8 +51,12 @@ public class ShanoirUploaderServiceClient { private static Logger logger = Logger.getLogger(ShanoirUploaderServiceClient.class); private static final String SHANOIR_SERVER_URL = "shanoir.server.url"; + + private static final String SERVICE_STUDIES_CREATE = "service.studies.create"; private static final String SERVICE_STUDIES_NAMES_CENTERS = "service.studies.names.centers"; + + private static final String SERVICE_STUDYCARDS_CREATE = "service.studycards.create"; private static final String SERVICE_STUDYCARDS_FIND_BY_STUDY_IDS = "service.studycards.find.by.study.ids"; @@ -86,8 +90,12 @@ public class ShanoirUploaderServiceClient { private String serverURL; + private String serviceURLStudiesCreate; + private String serviceURLStudiesNamesAndCenters; + private String serviceURLStudyCardsCreate; + private String serviceURLStudyCardsByStudyIds; private String serviceURLStudyCardsApplyOnStudy; @@ -96,14 +104,14 @@ public class ShanoirUploaderServiceClient { private String serviceURLAcquisitionEquipments; + private String serviceURLSubjectsCreate; + private String serviceURLSubjectsFindByIdentifier; private String serviceURLDatasets; private String serviceURLDatasetsDicomWebStudies; - private String serviceURLSubjectsCreate; - private String serviceURLExaminationsCreate; private String serviceURLImporterCreateTempDir; @@ -133,8 +141,12 @@ public ShanoirUploaderServiceClient() { this.serverURL = ShUpConfig.profileProperties.getProperty(SHANOIR_SERVER_URL); + this.serviceURLStudiesCreate = this.serverURL + + ShUpConfig.endpointProperties.getProperty(SERVICE_STUDIES_CREATE); this.serviceURLStudiesNamesAndCenters = this.serverURL + ShUpConfig.endpointProperties.getProperty(SERVICE_STUDIES_NAMES_CENTERS); + this.serviceURLStudyCardsCreate = this.serverURL + + ShUpConfig.endpointProperties.getProperty(SERVICE_STUDYCARDS_CREATE); this.serviceURLStudyCardsByStudyIds = this.serverURL + ShUpConfig.endpointProperties.getProperty(SERVICE_STUDYCARDS_FIND_BY_STUDY_IDS); this.serviceURLStudyCardsApplyOnStudy = this.serverURL @@ -521,6 +533,48 @@ public CloseableHttpResponse downloadDatasetsByStudyId(Long studyId, String form return null; } + public Study createStudy(final Study study) { + try { + String json = Util.objectWriter.writeValueAsString(study); + try (CloseableHttpResponse response = httpService.post(this.serviceURLStudiesCreate, json, false)) { + int code = response.getCode(); + if (code == HttpStatus.SC_OK) { + Study studyCreated = Util.getMappedObject(response, Study.class); + return studyCreated; + } else { + logger.error("Error in createStudy: with study " + study.getName() + + " (status code: " + code + ", message: " + apiResponseMessages.getOrDefault(code, "unknown status code") + ")"); + } + } + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } catch (IOException ioE) { + logger.error(ioE.getMessage(), ioE); + } + return null; + } + + public Center createStudyCard(final StudyCard studyCard) { + try { + String json = Util.objectWriter.writeValueAsString(studyCard); + try (CloseableHttpResponse response = httpService.post(this.serviceURLStudyCardsCreate, json, false)) { + int code = response.getCode(); + if (code == HttpStatus.SC_OK) { + Center centerCreated = Util.getMappedObject(response, Center.class); + return centerCreated; + } else { + logger.error("Error in createStudy: with study " + studyCard.getName() + + " (status code: " + code + ", message: " + apiResponseMessages.getOrDefault(code, "unknown status code") + ")"); + } + } + } catch (JsonProcessingException e) { + logger.error(e.getMessage(), e); + } catch (IOException ioE) { + logger.error(ioE.getMessage(), ioE); + } + return null; + } + public Center createCenter(final Center center) { try { String json = Util.objectWriter.writeValueAsString(center); diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/upload/UploadServiceJob.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/upload/UploadServiceJob.java index 73a37167c4..86d00bf28e 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/upload/UploadServiceJob.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/upload/UploadServiceJob.java @@ -94,7 +94,7 @@ private void processFolderForServer(final File folder, final UploadJobManager up final File uploadJobFile, CurrentNominativeDataController currentNominativeDataController) { NominativeDataUploadJobManager nominativeDataUploadJobManager = null; final List<File> filesToTransfer = new ArrayList<File>(); - final Collection<File> files = Util.listFiles(folder, null, false); + final Collection<File> files = Util.listFiles(folder, null, true); for (Iterator<File> filesIt = files.iterator(); filesIt.hasNext();) { final File file = (File) filesIt.next(); // do not transfer nominativeDataUploadJob as only for display in ShUp diff --git a/shanoir-uploader/src/main/java/org/shanoir/uploader/utils/ImportUtils.java b/shanoir-uploader/src/main/java/org/shanoir/uploader/utils/ImportUtils.java index 95fe8be125..d5bc44d4d0 100644 --- a/shanoir-uploader/src/main/java/org/shanoir/uploader/utils/ImportUtils.java +++ b/shanoir-uploader/src/main/java/org/shanoir/uploader/utils/ImportUtils.java @@ -21,6 +21,7 @@ import org.shanoir.uploader.action.DicomDataTransferObject; import org.shanoir.uploader.dicom.IDicomServerClient; import org.shanoir.uploader.dicom.query.SerieTreeNode; +import org.shanoir.uploader.dicom.retrieve.DcmRcvManager; import org.shanoir.uploader.model.rest.IdName; import org.shanoir.uploader.model.rest.Study; import org.shanoir.uploader.model.rest.StudyCard; @@ -290,7 +291,7 @@ public static List<String> copyFilesToUploadFolder(ImagesCreatorAndDicomFileAnal List<String> newFileNamesOfSerie = new ArrayList<String>(); for (Instance instance : serie.getInstances()) { File sourceFile = dicomFileAnalyzer.getFileFromInstance(instance, serie, filePathDicomDir, false); - String dicomFileName = sourceFile.getAbsolutePath().replace(File.separator, "_"); + String dicomFileName = sourceFile.getAbsolutePath().replace(File.separator, "_") + DcmRcvManager.DICOM_FILE_SUFFIX; File destFile = new File(uploadFolder.getAbsolutePath() + File.separator + dicomFileName); FileUtil.copyFile(sourceFile, destFile); newFileNamesOfSerie.add(dicomFileName); diff --git a/shanoir-uploader/src/main/resources/endpoint.properties b/shanoir-uploader/src/main/resources/endpoint.properties index ef1959ded2..ebcd3a1643 100644 --- a/shanoir-uploader/src/main/resources/endpoint.properties +++ b/shanoir-uploader/src/main/resources/endpoint.properties @@ -1,5 +1,6 @@ -service.studies=/shanoir-ng/studies/studies/ +service.studies.create=/shanoir-ng/studies/studies service.studies.names.centers=/shanoir-ng/studies/studies/namesAndCenters +service.studycards.create=/shanoir-ng/datasets/studycards service.studycards.find.by.study.ids=/shanoir-ng/datasets/studycards/search service.studycards.apply.on.study=/shanoir-ng/datasets/studycards/apply_on_study/ service.centers.create=/shanoir-ng/studies/centers diff --git a/shanoir-uploader/src/main/resources/profile.Neurinfo-Qualif/profile.properties b/shanoir-uploader/src/main/resources/profile.Neurinfo-Qualif/profile.properties index 5d3c73d84a..f22c46a13a 100644 --- a/shanoir-uploader/src/main/resources/profile.Neurinfo-Qualif/profile.properties +++ b/shanoir-uploader/src/main/resources/profile.Neurinfo-Qualif/profile.properties @@ -14,16 +14,7 @@ mode.subject.study.identifier=true anonymization.profile=Profile Neurinfo -########################################################## -# Server properties for accessing to Shanoir-old server -########################################################## -shanoir.server.user.name= -shanoir.server.user.password= -shanoir.server.uploader.service.url= -shanoir.server.uploader.service.qname.local.part= -shanoir.server.uploader.service.qname.namespace.uri= - ########################################################## # Server properties for accessing to Shanoir-NG server ########################################################## -shanoir.server.url=https\://shanoir-qualif.irisa.fr +shanoir.server.url=https\://shanoir-qualif.irisa.fr \ No newline at end of file diff --git a/shanoir-uploader/src/main/resources/profile.Neurinfo/profile.properties b/shanoir-uploader/src/main/resources/profile.Neurinfo/profile.properties index bd3aae3d7c..e39fd64dc6 100644 --- a/shanoir-uploader/src/main/resources/profile.Neurinfo/profile.properties +++ b/shanoir-uploader/src/main/resources/profile.Neurinfo/profile.properties @@ -14,16 +14,7 @@ mode.subject.study.identifier=true anonymization.profile=Profile Neurinfo -########################################################## -# Server properties for accessing to Shanoir-old server -########################################################## -shanoir.server.user.name= -shanoir.server.user.password= -shanoir.server.uploader.service.url= -shanoir.server.uploader.service.qname.local.part= -shanoir.server.uploader.service.qname.namespace.uri= - ########################################################## # Server properties for accessing to Shanoir-NG server ########################################################## -shanoir.server.url=https\://shanoir.irisa.fr +shanoir.server.url=https\://shanoir.irisa.fr \ No newline at end of file diff --git a/shanoir-uploader/src/main/resources/profile.OFSEP-Qualif/profile.properties b/shanoir-uploader/src/main/resources/profile.OFSEP-Qualif/profile.properties index a4f3217193..995774c032 100644 --- a/shanoir-uploader/src/main/resources/profile.OFSEP-Qualif/profile.properties +++ b/shanoir-uploader/src/main/resources/profile.OFSEP-Qualif/profile.properties @@ -14,16 +14,7 @@ mode.subject.study.identifier=false anonymization.profile=Profile OFSEP -########################################################## -# Server properties for accessing to Shanoir-old server -########################################################## -shanoir.server.user.name= -shanoir.server.user.password= -shanoir.server.uploader.service.url= -shanoir.server.uploader.service.qname.local.part= -shanoir.server.uploader.service.qname.namespace.uri= - ########################################################## # Server properties for accessing to Shanoir-NG server ########################################################## -shanoir.server.url=https\://shanoir-ofsep-qualif.irisa.fr +shanoir.server.url=https\://shanoir-ofsep-qualif.irisa.fr \ No newline at end of file diff --git a/shanoir-uploader/src/main/resources/profile.OFSEP/profile.properties b/shanoir-uploader/src/main/resources/profile.OFSEP/profile.properties index 352f6355f3..e2d4366fff 100644 --- a/shanoir-uploader/src/main/resources/profile.OFSEP/profile.properties +++ b/shanoir-uploader/src/main/resources/profile.OFSEP/profile.properties @@ -14,16 +14,7 @@ mode.subject.study.identifier=false anonymization.profile=Profile OFSEP -########################################################## -# Server properties for accessing to Shanoir-old server -########################################################## -shanoir.server.user.name= -shanoir.server.user.password= -shanoir.server.uploader.service.url= -shanoir.server.uploader.service.qname.local.part= -shanoir.server.uploader.service.qname.namespace.uri= - ########################################################## # Server properties for accessing to Shanoir-NG server ########################################################## -shanoir.server.url=https\://shanoir-ofsep.irisa.fr +shanoir.server.url=https\://shanoir-ofsep.irisa.fr \ No newline at end of file diff --git a/shanoir-uploader/src/main/resources/profile.dev/profile.properties b/shanoir-uploader/src/main/resources/profile.dev/profile.properties index dcfcf1bd7a..5a94ca403f 100644 --- a/shanoir-uploader/src/main/resources/profile.dev/profile.properties +++ b/shanoir-uploader/src/main/resources/profile.dev/profile.properties @@ -14,16 +14,7 @@ mode.subject.study.identifier=true anonymization.profile=Profile Neurinfo -########################################################## -# Server properties for accessing to Shanoir-old server -########################################################## -shanoir.server.user.name= -shanoir.server.user.password= -shanoir.server.uploader.service.url= -shanoir.server.uploader.service.qname.local.part= -shanoir.server.uploader.service.qname.namespace.uri= - ########################################################## # Server properties for accessing to Shanoir-NG server ########################################################## -shanoir.server.url=https\://shanoir-ng-nginx +shanoir.server.url=https\://shanoir-ng-nginx \ No newline at end of file diff --git a/shanoir-uploader/src/test/java/org/shanoir/uploader/test/importer/ZipFileImportTest.java b/shanoir-uploader/src/test/java/org/shanoir/uploader/test/importer/ZipFileImportTest.java index 846922acbc..57e5f38251 100644 --- a/shanoir-uploader/src/test/java/org/shanoir/uploader/test/importer/ZipFileImportTest.java +++ b/shanoir-uploader/src/test/java/org/shanoir/uploader/test/importer/ZipFileImportTest.java @@ -13,11 +13,13 @@ import org.shanoir.ng.importer.model.Patient; import org.shanoir.ng.importer.model.Serie; import org.shanoir.ng.importer.model.Study; +import org.shanoir.uploader.model.rest.Center; import org.shanoir.uploader.model.rest.Examination; import org.shanoir.uploader.model.rest.HemisphericDominance; import org.shanoir.uploader.model.rest.IdName; import org.shanoir.uploader.model.rest.ImagedObjectCategory; import org.shanoir.uploader.model.rest.Sex; +import org.shanoir.uploader.model.rest.StudyCenter; import org.shanoir.uploader.model.rest.Subject; import org.shanoir.uploader.model.rest.SubjectStudy; import org.shanoir.uploader.model.rest.SubjectType; @@ -30,13 +32,13 @@ public class ZipFileImportTest extends AbstractTest { private static Logger logger = Logger.getLogger(ZipFileImportTest.class); + private static final String ACR_PHANTOM_T1_ZIP = "acr_phantom_t1.zip"; + @Test public void importDicomZipTest() throws Exception { - org.shanoir.uploader.model.rest.Study study = new org.shanoir.uploader.model.rest.Study(); - study.setId(Long.valueOf(3)); - study.setName("DemoStudy"); + org.shanoir.uploader.model.rest.Study study = createStudyAndStudyCard(); for (int i = 0; i < 1; i++) { - ImportJob importJob = step1UploadDicom("acr_phantom_t1.zip"); + ImportJob importJob = step1UploadDicom(ACR_PHANTOM_T1_ZIP); if (!importJob.getPatients().isEmpty()) { selectAllSeriesForImport(importJob); Subject subject = step2CreateSubject(importJob, study); @@ -45,6 +47,22 @@ public void importDicomZipTest() throws Exception { } } } + + private org.shanoir.uploader.model.rest.Study createStudyAndStudyCard() { + org.shanoir.uploader.model.rest.Study study = new org.shanoir.uploader.model.rest.Study(); + final String randomStudyName = "Study-Name-" + UUID.randomUUID().toString(); + study.setName(randomStudyName); + study.setStudyStatus("IN_PROGRESS"); + List<StudyCenter> studyCenterList = new ArrayList<StudyCenter>(); + final StudyCenter studyCenter = new StudyCenter(); + final Center center = new Center(); + center.setId(Long.valueOf(1)); + studyCenter.setCenter(center); + studyCenterList.add(studyCenter); + study.setStudyCenterList(studyCenterList); + shUpClient.createStudy(study); + return study; + } private void createSubjectStudy(org.shanoir.uploader.model.rest.Study study, Subject subject) { SubjectStudy subjectStudy = new SubjectStudy();