feat: Multiframe metadata provider (#2938)
* fix(volume):Support volume viewing * fix(multiframe):Display multiframe US, and multiframe PR fixes * fix(metadataProvider):Don't call combine if frame not defined
This commit is contained in:
parent
3900e62641
commit
ff24b57a4b
@ -1,4 +1,5 @@
|
|||||||
import { isImage } from '@ohif/core/src/utils/isImage';
|
import { isImage } from '@ohif/core/src/utils/isImage';
|
||||||
|
import sopClassDictionary from '@ohif/core/src/utils/sopClassDictionary';
|
||||||
import ImageSet from '@ohif/core/src/classes/ImageSet';
|
import ImageSet from '@ohif/core/src/classes/ImageSet';
|
||||||
import isDisplaySetReconstructable from '@ohif/core/src/utils/isDisplaySetReconstructable';
|
import isDisplaySetReconstructable from '@ohif/core/src/utils/isDisplaySetReconstructable';
|
||||||
import { id } from './id';
|
import { id } from './id';
|
||||||
@ -143,138 +144,6 @@ function getDisplaySetsFromSeries(instances) {
|
|||||||
return displaySets;
|
return displaySets;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove since we have roughly the same thing in dcmjs
|
|
||||||
const sopClassDictionary = {
|
|
||||||
ComputedRadiographyImageStorage: '1.2.840.10008.5.1.4.1.1.1',
|
|
||||||
DigitalXRayImageStorageForPresentation: '1.2.840.10008.5.1.4.1.1.1.1',
|
|
||||||
DigitalXRayImageStorageForProcessing: '1.2.840.10008.5.1.4.1.1.1.1.1',
|
|
||||||
DigitalMammographyXRayImageStorageForPresentation:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.1.2',
|
|
||||||
DigitalMammographyXRayImageStorageForProcessing:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.1.2.1',
|
|
||||||
DigitalIntraOralXRayImageStorageForPresentation:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.1.3',
|
|
||||||
DigitalIntraOralXRayImageStorageForProcessing:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.1.3.1',
|
|
||||||
CTImageStorage: '1.2.840.10008.5.1.4.1.1.2',
|
|
||||||
EnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.1',
|
|
||||||
LegacyConvertedEnhancedCTImageStorage: '1.2.840.10008.5.1.4.1.1.2.2',
|
|
||||||
UltrasoundMultiframeImageStorage: '1.2.840.10008.5.1.4.1.1.3.1',
|
|
||||||
MRImageStorage: '1.2.840.10008.5.1.4.1.1.4',
|
|
||||||
EnhancedMRImageStorage: '1.2.840.10008.5.1.4.1.1.4.1',
|
|
||||||
MRSpectroscopyStorage: '1.2.840.10008.5.1.4.1.1.4.2',
|
|
||||||
EnhancedMRColorImageStorage: '1.2.840.10008.5.1.4.1.1.4.3',
|
|
||||||
LegacyConvertedEnhancedMRImageStorage: '1.2.840.10008.5.1.4.1.1.4.4',
|
|
||||||
UltrasoundImageStorageRET: '1.2.840.10008.5.1.4.1.1.6',
|
|
||||||
UltrasoundImageStorage: '1.2.840.10008.5.1.4.1.1.6.1',
|
|
||||||
EnhancedUSVolumeStorage: '1.2.840.10008.5.1.4.1.1.6.2',
|
|
||||||
SecondaryCaptureImageStorage: '1.2.840.10008.5.1.4.1.1.7',
|
|
||||||
MultiframeSingleBitSecondaryCaptureImageStorage:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.7.1',
|
|
||||||
MultiframeGrayscaleByteSecondaryCaptureImageStorage:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.7.2',
|
|
||||||
MultiframeGrayscaleWordSecondaryCaptureImageStorage:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.7.3',
|
|
||||||
MultiframeTrueColorSecondaryCaptureImageStorage:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.7.4',
|
|
||||||
Sop12LeadECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.1',
|
|
||||||
GeneralECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.2',
|
|
||||||
AmbulatoryECGWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.1.3',
|
|
||||||
HemodynamicWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.2.1',
|
|
||||||
CardiacElectrophysiologyWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.3.1',
|
|
||||||
BasicVoiceAudioWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.4.1',
|
|
||||||
GeneralAudioWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.4.2',
|
|
||||||
ArterialPulseWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.5.1',
|
|
||||||
RespiratoryWaveformStorage: '1.2.840.10008.5.1.4.1.1.9.6.1',
|
|
||||||
GrayscaleSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.1',
|
|
||||||
ColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.2',
|
|
||||||
PseudoColorSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.3',
|
|
||||||
BlendingSoftcopyPresentationStateStorage: '1.2.840.10008.5.1.4.1.1.11.4',
|
|
||||||
XAXRFGrayscaleSoftcopyPresentationStateStorage:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.11.5',
|
|
||||||
XRayAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.12.1',
|
|
||||||
EnhancedXAImageStorage: '1.2.840.10008.5.1.4.1.1.12.1.1',
|
|
||||||
XRayRadiofluoroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.12.2',
|
|
||||||
EnhancedXRFImageStorage: '1.2.840.10008.5.1.4.1.1.12.2.1',
|
|
||||||
XRay3DAngiographicImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.1',
|
|
||||||
XRay3DCraniofacialImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.2',
|
|
||||||
BreastTomosynthesisImageStorage: '1.2.840.10008.5.1.4.1.1.13.1.3',
|
|
||||||
BreastProjectionXRayImageStorageForPresentation:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.13.1.4',
|
|
||||||
BreastProjectionXRayImageStorageForProcessing:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.13.1.5',
|
|
||||||
IntravascularOpticalCoherenceTomographyImageStorageForPresentation:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.14.1',
|
|
||||||
IntravascularOpticalCoherenceTomographyImageStorageForProcessing:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.14.2',
|
|
||||||
NuclearMedicineImageStorage: '1.2.840.10008.5.1.4.1.1.20',
|
|
||||||
RawDataStorage: '1.2.840.10008.5.1.4.1.1.66',
|
|
||||||
SpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.1',
|
|
||||||
SpatialFiducialsStorage: '1.2.840.10008.5.1.4.1.1.66.2',
|
|
||||||
DeformableSpatialRegistrationStorage: '1.2.840.10008.5.1.4.1.1.66.3',
|
|
||||||
SegmentationStorage: '1.2.840.10008.5.1.4.1.1.66.4',
|
|
||||||
SurfaceSegmentationStorage: '1.2.840.10008.5.1.4.1.1.66.5',
|
|
||||||
RealWorldValueMappingStorage: '1.2.840.10008.5.1.4.1.1.67',
|
|
||||||
SurfaceScanMeshStorage: '1.2.840.10008.5.1.4.1.1.68.1',
|
|
||||||
SurfaceScanPointCloudStorage: '1.2.840.10008.5.1.4.1.1.68.2',
|
|
||||||
VLEndoscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.1',
|
|
||||||
VideoEndoscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.1.1',
|
|
||||||
VLMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.2',
|
|
||||||
VideoMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.2.1',
|
|
||||||
VLSlideCoordinatesMicroscopicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.3',
|
|
||||||
VLPhotographicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.4',
|
|
||||||
VideoPhotographicImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.4.1',
|
|
||||||
OphthalmicPhotography8BitImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.1',
|
|
||||||
OphthalmicPhotography16BitImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.2',
|
|
||||||
StereometricRelationshipStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.3',
|
|
||||||
OphthalmicTomographyImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.5.4',
|
|
||||||
VLWholeSlideMicroscopyImageStorage: '1.2.840.10008.5.1.4.1.1.77.1.6',
|
|
||||||
LensometryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.1',
|
|
||||||
AutorefractionMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.2',
|
|
||||||
KeratometryMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.3',
|
|
||||||
SubjectiveRefractionMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.4',
|
|
||||||
VisualAcuityMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.5',
|
|
||||||
SpectaclePrescriptionReportStorage: '1.2.840.10008.5.1.4.1.1.78.6',
|
|
||||||
OphthalmicAxialMeasurementsStorage: '1.2.840.10008.5.1.4.1.1.78.7',
|
|
||||||
IntraocularLensCalculationsStorage: '1.2.840.10008.5.1.4.1.1.78.8',
|
|
||||||
MacularGridThicknessandVolumeReport: '1.2.840.10008.5.1.4.1.1.79.1',
|
|
||||||
OphthalmicVisualFieldStaticPerimetryMeasurementsStorage:
|
|
||||||
'1.2.840.10008.5.1.4.1.1.80.1',
|
|
||||||
OphthalmicThicknessMapStorage: '1.2.840.10008.5.1.4.1.1.81.1',
|
|
||||||
CornealTopographyMapStorage: '1.2.840.10008.5.1.4.1.1.82.1',
|
|
||||||
BasicTextSR: '1.2.840.10008.5.1.4.1.1.88.11',
|
|
||||||
EnhancedSR: '1.2.840.10008.5.1.4.1.1.88.22',
|
|
||||||
ComprehensiveSR: '1.2.840.10008.5.1.4.1.1.88.33',
|
|
||||||
Comprehensive3DSR: '1.2.840.10008.5.1.4.1.1.88.34',
|
|
||||||
ProcedureLog: '1.2.840.10008.5.1.4.1.1.88.40',
|
|
||||||
MammographyCADSR: '1.2.840.10008.5.1.4.1.1.88.50',
|
|
||||||
KeyObjectSelection: '1.2.840.10008.5.1.4.1.1.88.59',
|
|
||||||
ChestCADSR: '1.2.840.10008.5.1.4.1.1.88.65',
|
|
||||||
XRayRadiationDoseSR: '1.2.840.10008.5.1.4.1.1.88.67',
|
|
||||||
RadiopharmaceuticalRadiationDoseSR: '1.2.840.10008.5.1.4.1.1.88.68',
|
|
||||||
ColonCADSR: '1.2.840.10008.5.1.4.1.1.88.69',
|
|
||||||
ImplantationPlanSRDocumentStorage: '1.2.840.10008.5.1.4.1.1.88.70',
|
|
||||||
EncapsulatedPDFStorage: '1.2.840.10008.5.1.4.1.1.104.1',
|
|
||||||
EncapsulatedCDAStorage: '1.2.840.10008.5.1.4.1.1.104.2',
|
|
||||||
PositronEmissionTomographyImageStorage: '1.2.840.10008.5.1.4.1.1.128',
|
|
||||||
EnhancedPETImageStorage: '1.2.840.10008.5.1.4.1.1.130',
|
|
||||||
LegacyConvertedEnhancedPETImageStorage: '1.2.840.10008.5.1.4.1.1.128.1',
|
|
||||||
BasicStructuredDisplayStorage: '1.2.840.10008.5.1.4.1.1.131',
|
|
||||||
RTImageStorage: '1.2.840.10008.5.1.4.1.1.481.1',
|
|
||||||
RTDoseStorage: '1.2.840.10008.5.1.4.1.1.481.2',
|
|
||||||
RTStructureSetStorage: '1.2.840.10008.5.1.4.1.1.481.3',
|
|
||||||
RTBeamsTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.4',
|
|
||||||
RTPlanStorage: '1.2.840.10008.5.1.4.1.1.481.5',
|
|
||||||
RTBrachyTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.6',
|
|
||||||
RTTreatmentSummaryRecordStorage: '1.2.840.10008.5.1.4.1.1.481.7',
|
|
||||||
RTIonPlanStorage: '1.2.840.10008.5.1.4.1.1.481.8',
|
|
||||||
RTIonBeamsTreatmentRecordStorage: '1.2.840.10008.5.1.4.1.1.481.9',
|
|
||||||
RTBeamsDeliveryInstructionStorage: '1.2.840.10008.5.1.4.34.7',
|
|
||||||
GenericImplantTemplateStorage: '1.2.840.10008.5.1.4.43.1',
|
|
||||||
ImplantAssemblyTemplateStorage: '1.2.840.10008.5.1.4.44.1',
|
|
||||||
ImplantTemplateGroupStorage: '1.2.840.10008.5.1.4.45.1',
|
|
||||||
};
|
|
||||||
|
|
||||||
const sopClassUids = [
|
const sopClassUids = [
|
||||||
sopClassDictionary.ComputedRadiographyImageStorage,
|
sopClassDictionary.ComputedRadiographyImageStorage,
|
||||||
sopClassDictionary.DigitalXRayImageStorageForPresentation,
|
sopClassDictionary.DigitalXRayImageStorageForPresentation,
|
||||||
@ -325,6 +194,7 @@ const sopClassUids = [
|
|||||||
sopClassDictionary.EnhancedPETImageStorage,
|
sopClassDictionary.EnhancedPETImageStorage,
|
||||||
sopClassDictionary.LegacyConvertedEnhancedPETImageStorage,
|
sopClassDictionary.LegacyConvertedEnhancedPETImageStorage,
|
||||||
sopClassDictionary.RTImageStorage,
|
sopClassDictionary.RTImageStorage,
|
||||||
|
sopClassDictionary.EnhancedUSVolumeStorage,
|
||||||
];
|
];
|
||||||
|
|
||||||
function getSopClassHandlerModule() {
|
function getSopClassHandlerModule() {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import getPixelSpacingInformation from '../utils/metadataProvider/getPixelSpacin
|
|||||||
import DicomMetadataStore from '../services/DicomMetadataStore';
|
import DicomMetadataStore from '../services/DicomMetadataStore';
|
||||||
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
|
import fetchPaletteColorLookupTableData from '../utils/metadataProvider/fetchPaletteColorLookupTableData';
|
||||||
import toNumber from '../utils/toNumber';
|
import toNumber from '../utils/toNumber';
|
||||||
|
import combineFrameInstance from '../utils/combineFrameInstance';
|
||||||
|
|
||||||
class MetadataProvider {
|
class MetadataProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -57,13 +58,21 @@ class MetadataProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = uids;
|
const {
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
frameNumber,
|
||||||
|
} = uids;
|
||||||
|
|
||||||
return DicomMetadataStore.getInstance(
|
const instance = DicomMetadataStore.getInstance(
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
SeriesInstanceUID,
|
SeriesInstanceUID,
|
||||||
SOPInstanceUID
|
SOPInstanceUID
|
||||||
);
|
);
|
||||||
|
return (
|
||||||
|
(frameNumber && combineFrameInstance(frameNumber, instance)) || instance
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(query, imageId, options = { fallback: false }) {
|
get(query, imageId, options = { fallback: false }) {
|
||||||
@ -408,7 +417,10 @@ class MetadataProvider {
|
|||||||
// SOPInstanceUID,
|
// SOPInstanceUID,
|
||||||
// })
|
// })
|
||||||
// somewhere else
|
// somewhere else
|
||||||
if (imageId.startsWith('wadors:')) {
|
if (
|
||||||
|
imageId.startsWith('wadors:') ||
|
||||||
|
imageId.startsWith('streaming-wadors:')
|
||||||
|
) {
|
||||||
const strippedImageId = imageId.split('/studies/')[1];
|
const strippedImageId = imageId.split('/studies/')[1];
|
||||||
const splitImageId = strippedImageId.split('/');
|
const splitImageId = strippedImageId.split('/');
|
||||||
|
|
||||||
@ -416,6 +428,7 @@ class MetadataProvider {
|
|||||||
StudyInstanceUID: splitImageId[0], // Note: splitImageId[1] === 'series'
|
StudyInstanceUID: splitImageId[0], // Note: splitImageId[1] === 'series'
|
||||||
SeriesInstanceUID: splitImageId[2], // Note: splitImageId[3] === 'instances'
|
SeriesInstanceUID: splitImageId[2], // Note: splitImageId[3] === 'instances'
|
||||||
SOPInstanceUID: splitImageId[4],
|
SOPInstanceUID: splitImageId[4],
|
||||||
|
frameNumber: splitImageId[6],
|
||||||
};
|
};
|
||||||
} else if (imageId.includes('?requestType=WADO')) {
|
} else if (imageId.includes('?requestType=WADO')) {
|
||||||
const qs = queryString.parse(imageId);
|
const qs = queryString.parse(imageId);
|
||||||
@ -424,6 +437,7 @@ class MetadataProvider {
|
|||||||
StudyInstanceUID: qs.studyUID,
|
StudyInstanceUID: qs.studyUID,
|
||||||
SeriesInstanceUID: qs.seriesUID,
|
SeriesInstanceUID: qs.seriesUID,
|
||||||
SOPInstanceUID: qs.objectUID,
|
SOPInstanceUID: qs.objectUID,
|
||||||
|
frameNumber: qs.frameNumber,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,7 +452,13 @@ class MetadataProvider {
|
|||||||
imageURI = imageIdToURI(imageId);
|
imageURI = imageIdToURI(imageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.imageURIToUIDs.get(imageURI);
|
const uids = this.imageURIToUIDs.get(imageURI);
|
||||||
|
const frameNumber = imageId.split(/\/frames\//)[1];
|
||||||
|
|
||||||
|
if (uids && frameNumber !== undefined) {
|
||||||
|
return { ...uids, frameNumber };
|
||||||
|
}
|
||||||
|
return uids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
platform/core/src/utils/combineFrameInstance.ts
Normal file
33
platform/core/src/utils/combineFrameInstance.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Combine the Per instance frame data, the shared frame data
|
||||||
|
* and the root data objects.
|
||||||
|
* The data is combined by taking nested sequence objects within
|
||||||
|
* the functional group sequences. Data that is directly contained
|
||||||
|
* within the functional group sequences, such as private creators
|
||||||
|
* will be ignored.
|
||||||
|
* This can be safely called with an undefined frame in order to handle
|
||||||
|
* single frame data. (eg frame is undefined is the same as frame===1).
|
||||||
|
*/
|
||||||
|
const combineFrameInstance = (frame, instance) => {
|
||||||
|
const {
|
||||||
|
PerFrameFunctionalGroupsSequence,
|
||||||
|
SharedFunctionalGroupsSequence,
|
||||||
|
} = instance;
|
||||||
|
if (!PerFrameFunctionalGroupsSequence) return instance;
|
||||||
|
const shared = Object.values(SharedFunctionalGroupsSequence[0])
|
||||||
|
.map(it => it[0])
|
||||||
|
.filter(it => it !== undefined && typeof it === 'object');
|
||||||
|
const perFrame = Object.values(
|
||||||
|
PerFrameFunctionalGroupsSequence[(frame || 1) - 1]
|
||||||
|
)
|
||||||
|
.map(it => it[0])
|
||||||
|
.filter(it => it !== undefined && typeof it === 'object');
|
||||||
|
return Object.assign(
|
||||||
|
{},
|
||||||
|
instance,
|
||||||
|
...Object.values(shared),
|
||||||
|
...Object.values(perFrame)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default combineFrameInstance;
|
||||||
@ -12,6 +12,7 @@ const imagesTypes = [
|
|||||||
sopClassDictionary.EnhancedCTImageStorage,
|
sopClassDictionary.EnhancedCTImageStorage,
|
||||||
sopClassDictionary.LegacyConvertedEnhancedCTImageStorage,
|
sopClassDictionary.LegacyConvertedEnhancedCTImageStorage,
|
||||||
sopClassDictionary.UltrasoundMultiframeImageStorage,
|
sopClassDictionary.UltrasoundMultiframeImageStorage,
|
||||||
|
sopClassDictionary.EnhancedUSVolumeStorage,
|
||||||
sopClassDictionary.MRImageStorage,
|
sopClassDictionary.MRImageStorage,
|
||||||
sopClassDictionary.EnhancedMRImageStorage,
|
sopClassDictionary.EnhancedMRImageStorage,
|
||||||
sopClassDictionary.EnhancedMRColorImageStorage,
|
sopClassDictionary.EnhancedMRColorImageStorage,
|
||||||
|
|||||||
@ -128,3 +128,5 @@ export const sopClassDictionary = {
|
|||||||
ImplantAssemblyTemplateStorage: '1.2.840.10008.5.1.4.44.1',
|
ImplantAssemblyTemplateStorage: '1.2.840.10008.5.1.4.44.1',
|
||||||
ImplantTemplateGroupStorage: '1.2.840.10008.5.1.4.45.1',
|
ImplantTemplateGroupStorage: '1.2.840.10008.5.1.4.45.1',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default sopClassDictionary;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user