def _convert_to_png_and_store(_slice: Slice, slice_pixels: np.ndarray) -> None: """Convert given Slice's pixel array and store in databases. :param _slice: Slice database object :param slice_pixels: numpy array with Slice data """ converted_image = _convert_slice_pixels_to_png(slice_pixels) SlicesRepository.store_converted_image(_slice.id, converted_image) _slice.update_status(SliceStatus.PROCESSED) logger.info('%s converted and stored.', _slice)
def add_new_slice(scan_id: ScanID, image: bytes) -> Slice: """Add new Slice for given Scan. :param scan_id: ID of a Scan for which it should add new slice :param image: bytes representing Dicom image :return: Slice object """ scan = ScansRepository.get_scan_by_id(scan_id) _slice = scan.add_slice() SlicesRepository.store_original_image(_slice.id, image) parse_dicom_and_update_slice.delay(_slice.id) return _slice
def parse_dicom_and_update_slice(slice_id: SliceID) -> None: """Parse DICOM from Storage and update Slice for location and position. :param slice_id: ID of a slice """ logger.debug('Parsing DICOM file from Storage for given Slice ID: %s.', slice_id) _slice = SlicesRepository.get_slice_by_id(slice_id) image = SlicesRepository.get_slice_original_image(_slice.id) # We've got to store above DICOM image bytes on disk due to the fact that SimpleITK does not support # reading files from memory. It has to work on a file stored on a hard drive. temp_file = NamedTemporaryFile(delete=False) temp_file.write(image) temp_file.close() try: reader = sitk.ImageFileReader() reader.SetFileName(temp_file.name) reader.ReadImageInformation() location = SliceLocation( read_float(reader, DicomTag.SLICE_LOCATION) or 0.0) raw_position = read_list( reader, DicomTag.IMAGE_POSITION_PATIENT) or [0.0, 0.0, 0.0] position = SlicePosition(*list(map(float, raw_position))) height = read_int(reader, DicomTag.ROWS) or 0 width = read_int(reader, DicomTag.COLUMNS) or 0 except RuntimeError: logger.error('User sent a file that is not a DICOM.') SlicesRepository.delete_slice_by_id(_slice.id) ScansRepository.reduce_number_of_declared_slices(_slice.scan_id) os.unlink(temp_file.name) trigger_scan_conversion_if_needed(_slice.scan_id) return # Remove temporary file os.unlink(temp_file.name) _slice.update_location(location) _slice.update_position(position) _slice.update_size(height, width) _slice.update_status(SliceStatus.STORED) logger.info('"%s" updated.', _slice) trigger_scan_conversion_if_needed(_slice.scan_id)
def get_slices_for_scan( scan_id: ScanID, begin: int, count: int, orientation: SliceOrientation = SliceOrientation.Z ) -> Iterable[Tuple[Slice, bytes]]: """Fetch multiple slices for given scan. :param scan_id: ID of a given scan :param begin: first slice index (included) :param count: number of slices that will be returned :param orientation: orientation for Slices (by default set to Z axis) :return: generator for Slices """ slices = SlicesRepository.get_slices_by_scan_id(scan_id, orientation=orientation) for _slice in slices[begin:begin + count]: image = SlicesRepository.get_slice_converted_image(_slice.id) yield _slice, image
def convert_scan_to_png(scan_id: ScanID) -> None: """Convert DICOM Scan to PNG and save it into Storage. :param scan_id: ID of a Scan """ logger.info('Starting Scan (%s) conversion.', scan_id) temp_files_to_remove: List[str] = [] scan = ScansRepository.get_scan_by_id(scan_id) slices = SlicesRepository.get_slices_by_scan_id(scan_id) if scan.declared_number_of_slices == 0: logger.error('This Scan is empty! Removing from database...') ScansRepository.delete_scan_by_id(scan_id) return logger.info('Marking Scan as processing.') scan.update_status(ScanStatus.PROCESSING) # At first, collect all Dicom images for given Scan logger.info('Reading all Slices for this Scan... This may take a while...') dicom_images = [] for _slice in slices: image = SlicesRepository.get_slice_original_image(_slice.id) # We've got to store above DICOM image bytes on disk due to the fact that SimpleITK does not support # reading files from memory. It has to work on a file stored on a hard drive. temp_file = NamedTemporaryFile(delete=False) temp_file.write(image) temp_file.close() dicom_image = sitk.ReadImage(temp_file.name) dicom_images.append(dicom_image) temp_files_to_remove.append(temp_file.name) # Correlate Dicom files with Slices and convert all Slices _convert_scan_in_all_axes(dicom_images, slices, scan) logger.info('Marking Scan as available to use.') scan.update_status(ScanStatus.AVAILABLE) # Remove all temporarily created files for applying workaround for file_name in temp_files_to_remove: os.unlink(file_name)
def test_scan_upload_and_conversion(prepare_environment: Any, synchronous_celery: Any) -> None: """Test application for Scan upload and conversion.""" api_client = get_api_client() user_token = get_token_for_logged_in_user('admin') # Step 1. Add Scan to the system payload = {'category': 'LUNGS', 'number_of_slices': 3} response = api_client.post('/api/v1/scans/', data=json.dumps(payload), headers=get_headers(token=user_token, json=True)) json_response = json.loads(response.data) scan_id = json_response['scan_id'] # Step 2. Send Slices for file in glob.glob('tests/assets/example_scan/*.dcm'): with open(file, 'rb') as image: response = api_client.post( '/api/v1/scans/{}/slices'.format(scan_id), data={ 'image': (image, 'slice_1.dcm'), }, headers=get_headers(token=user_token, multipart=True)) assert response.status_code == 201 # Step 3. Check Scan & Slices in the databases z_slices = SlicesRepository.get_slices_by_scan_id(scan_id) assert len(z_slices) == 3 y_slices = SlicesRepository.get_slices_by_scan_id(scan_id, SliceOrientation.Y) assert not y_slices x_slices = SlicesRepository.get_slices_by_scan_id(scan_id, SliceOrientation.X) assert not x_slices # Step 3.1. Slices in Z axis z_slice = SlicesRepository.get_slice_converted_image(z_slices[2].id) z_slice_image = Image.open(io.BytesIO(z_slice)) assert z_slice_image.size == (512, 512)
scan.owner_id, 'slices': [{ 'id': scan_slice.id, 'location': scan_slice.location, 'position_x': scan_slice.position_x, 'position_y': scan_slice.position_y, 'position_z': scan_slice.position_z, 'stored': scan_slice.stored, 'converted': scan_slice.converted, } for scan_slice in scan.stored_slices], }) # Save DICOMs to the Scan directory for scan_slice in scan.stored_slices: slice_file_name = scan_directory + str(scan_slice.id) + '.dcm' slice_bytes = SlicesRepository.get_slice_original_image(scan_slice.id) print('Saving DICOM to file: {}'.format(slice_file_name)) with open(slice_file_name, 'wb') as slice_file: slice_file.write(slice_bytes) print('\nSaving all Scans to file: {}'.format(SCANS_METADATA_FILE)) with open(SCANS_METADATA_FILE, 'w') as json_file: json.dump(_scans, json_file) # Save Labels to JSON file _labels = { 'labels': [], } # type: Dict[str, List[Dict]] print('\nFetching all Labels...') for label in LabelsRepository.get_all_labels(): print('Saving Label: {}'.format(label.id)) _labels['labels'].append({