Пример #1
0
def anonymize_and_upload(R: DicomRegistry, D: DicomDirectory, O: Orthanc):

    for d in R.instances.values():

        ser_mhash = Dixel.from_child(d).mhash
        # Find the real ser
        ser = R.get(ser_mhash, dlvl=DLv.SERIES)

        stu_mhash = Dixel.from_child(d, DLv.STUDY).mhash
        # Find the real stu
        stu = R.get(stu_mhash, dlvl=DLv.STUDY)

        m = orthanc_sham_map(
            stu.mhash,
            stu.dhash,
            # Try patient id first, fall back to patient name, or use "UNKNOWN"
            patient_id=stu.tags.get("PatientID",
                                    stu.tags.get("PatientName", "UNKNOWN")),
            stu_dt=stu.timestamp,
            ser_mhash=ser.mhash,
            ser_dhash=ser.dhash,
            ser_dt=ser.timestamp,
            inst_mhash=d.mhash,
            inst_dhash=d.dhash,
            inst_dt=d.timestamp)

        dd = D.get(d.meta["fp"], binary=True)
        r = O.put(dd)  # If no Patient ID, need to use returned value
        oid = r['ID']
        print(f"Putting {dd.main_tags()} in {oid}")
        O.anonymize(oid,
                    replacement_map=m)  # Anon inst no longer returns image?
        O.delete(oid, dlvl=DLv.INSTANCE)
Пример #2
0
    def get(self,
            oid: UID,
            dlvl: DLv = DLv.STUDY,
            binary: bool = False,
            view: str = None,
            **kwargs) -> typ.Union[Dixel, typ.Dict]:

        # Want to get the raw Orthanc metadata w child info
        if view == "raw":
            resource = f"{dlvl.opath()}/{oid}"
            metadata = self.request(resource, **kwargs)
            return metadata

        if dlvl == DLv.INSTANCE:
            resource = f"{dlvl.opath()}/{oid}/tags?simplify"
        else:
            resource = f"{dlvl.opath()}/{oid}/shared-tags?simplify"
        tags = self.request(resource, **kwargs)

        # pprint(tags)

        d = Dixel.from_tags(tags, dlvl=dlvl)
        if binary:
            if dlvl > DLv.INSTANCE:
                route = "archive"
            else:
                route = "file"
            resource = f"{dlvl.opath()}/{oid}/{route}"
            _file = self.request(resource, **kwargs)
            if _file:
                d.file = _file
        return d
Пример #3
0
    def put(self, inst: Dixel, **kwargs):

        self.dhashes[self.t(inst, KTy.DHASH)] = inst
        self.bhashes[self.t(inst, KTy.BHASH)] = inst
        self.instances[self.t(inst)] = inst

        ser = Dixel.from_child(inst, dlvl=DLv.SERIES)
        if not self.series.get(self.t(ser)):
            self.series[self.t(ser)] = ser
        else:
            self.series[self.t(ser)].add_child(inst)

        stu = Dixel.from_child(inst, dlvl=DLv.STUDY)
        if not self.studies.get(self.t(stu)):
            self.studies[self.t(stu)] = stu
        else:
            self.studies[self.t(stu)].add_child(stu)
Пример #4
0
def measure_scout(dixel: Dixel):
    """Measure AP and Lateral dimensions from DICOM localizer images for SSDE calculations"""

    # TODO: add a tag check for "LOCALIZER"

    pixel_spacing = dixel.pixel_spacing

    def measured_dimension(dixel):
        """
        Determine measurement dimension

        Could also try to use "degrees of azimuth" to identify lateral vs. AP:
          - 90 its a lateral scout -> PA dimension
          - 0,180 its an AP scout -> lateral dimension
        """

        orientation = dixel.image_orientation_patient

        if orientation[0] * orientation[0] > 0.2:
            # This is a PA scout, which gives the lateral measurement
            measured_dim = 'LATERAL'
        elif orientation[1] * orientation[1] > 0.2:
            # This is a lateral scout, which gives the AP measurement
            measured_dim = 'AP'
        else:
            measured_dim = 'UNKNOWN'

        return measured_dim

    measured_dim = measured_dimension(dixel)

    # Setup pixel array data
    dcm_px = np.array(dixel.get_pixels(), dtype=np.float32)

    # Determine weighted threshold separating tissue/non-tissue attenuations
    # using a Gaussian Mixture Model
    # thresh = np.mean(dcm_px[dcm_px>0])

    gmm = GMM(2).fit(dcm_px[dcm_px > -1024].reshape(-1, 1))
    thresh = np.sum(gmm.weights_[::-1] * gmm.means_.ravel())

    # logging.debug(gmm.weights_[::-1])
    # logging.debug(gmm.means_.ravel())

    logging.debug("Threshold: {0}".format(thresh))

    # Compute avg width based on unmasked pixels
    mask = dcm_px > thresh

    px_counts = np.sum(mask, axis=1)
    avg_px_count = np.mean(px_counts[px_counts > 0])
    # Across image spacing is 2nd component (axis 1)
    d_avg = avg_px_count * pixel_spacing[1] / 10

    logging.debug("Average {0} width: {1}".format(measured_dim, d_avg))

    return (measured_dim, d_avg)
Пример #5
0
    def make_key(self, ids, source: Orthanc, domain: str) -> set:

        print("Making key")

        # Minimal data for oid and sham plus study and series desc
        def mkq(accession_num):
            return {
                "PatientName": "",
                "PatientID": "",
                "PatientBirthDate": "",
                "PatientSex": "",
                "AccessionNumber": accession_num,
                "StudyDescription": "",
                "StudyInstanceUID": "",
                "StudyDate": "",
                "StudyTime": "",
            }

        items = set()
        for id in ids:

            q = mkq(id)

            try:
                r = source.rfind(q, domain, level=DicomLevel.STUDIES)
            except:
                r = None

            if not r:
                print("Failed to collect an id")
                continue

            tags = {
                "PatientName": r[0]["PatientName"],
                "PatientID": r[0]["PatientID"],
                "PatientBirthDate": r[0]["PatientBirthDate"],
                "PatientSex": r[0]["PatientSex"],
                "AccessionNumber": r[0]["AccessionNumber"],
                "StudyDescription": r[0]["StudyDescription"],
                "StudyInstanceUID": r[0]["StudyInstanceUID"],
                "StudyDate": r[0]["StudyDate"],
                "StudyTime": r[0]["StudyTime"]
            }

            d = Dixel(tags=tags)
            e = ShamDixel.from_dixel(d)

            items.add(e)
            print("Found {} items".format(len(items)))

            logging.debug(e)

        return items
Пример #6
0
def get_pixels(dixel: Dixel, imsize=224):

    pixels = dixel.get_pixels()

    if dixel.meta.get("PhotometricInterpretation") == "MONOCHROME1":
        pixels = np.invert(pixels.astype("uint16"))

    pixels = pixels.astype("float32")
    pixels -= np.min(pixels)
    pixels /= np.max(pixels)
    pixels *= 255.
    pixels = resize_image(pixels, imsize, verbose=False)
    return pixels
Пример #7
0
def test_redis_index(setup_redis):

    R = Redis()

    d = Dixel(meta={"FileName": "my_file"},
              tags={"AccessionNumber": "100"})

    R.add_to_collection(d, item_key="FileName")
    logging.debug( R.collections() )
    assert("100" in R.collections() )

    logging.debug( R.collected_items("100") )
    assert("my_file" in R.collected_items("100") )
Пример #8
0
def test_orthanc_upload():

    O = Orthanc()
    O.clear()

    ROOTP = "~/data/dcm/ibis2"
    D = DicomDirectory(ROOTP)

    d = D.get("IM1", binary=True)
    print(d.summary())
    O.put(d)
    g = O.get(d.oid(), dlvl=DLv.INSTANCE)
    Dixel.comparator = CTy.METADATA
    assert d == g

    e = Dixel.from_child(d)
    h = O.get(e.oid(), dlvl=DLv.SERIES)
    assert e == h

    f = Dixel.from_child(e)
    i = O.get(f.oid(), dlvl=DLv.STUDY)
    assert f == i
Пример #9
0
def test_csv(tmp_path):

    fp = tmp_path / "tmp.csv"

    ep0 = CsvFile(fp=fp)
    ep0.fieldnames = ["_Age", "PatientName", "AccessionNumber"]

    for i in range(10):

        tags = {
            "PatientName": "Subject {}".format(i),
            "AccessionNumber": "Study {}".format(i)
        }

        meta = {
            "Age": 20+i
        }

        ep0.dixels.add(Dixel(tags=tags, meta=meta))

    ep0.write()

    ep1 = CsvFile(fp=fp)
    ep1.read()
    d = ep1.dixels.pop()

    logging.debug(d)

    assert( d.meta['Age']>="20" )
    assert( d.tags['PatientName'].startswith("Subject"))

    logging.debug(ep1.fieldnames)

    ep2 = CsvFile(fp=fp)
    ep2.fieldnames = ['_ShamID', '_ShamBirthDate', '_ShamAccessionNumber', 'PatientName', 'AccessionNumber']

    ShamDixel.REFERENCE_DATE = date(year=2018, month=1, day=1)
    for d in ep0.dixels:
        logging.debug(d)
        ep2.dixels.add( ShamDixel.from_dixel(d) )

    ep2.write()

    ep2.fieldnames = "ALL"
    ep2.write()

    os.remove(fp)
Пример #10
0
def test_shams():

    tags = {
        "PatientName": "FOOABC^BAR^Z",
        "PatientBirthDate": "20000101",
        "StudyDate": "20180102",
        "StudyTime": "120001",
        "AccessionNumber": "12345678"
    }

    expected_meta = {
        'ShamID': 'NTE3OTYH32XNES3LPNKR2U6CVOCZXDIL',
        'ShamName': 'NADERMAN^TRACY^E',
        'ShamBirthDate': "19991009",
        'ShamStudyDateTime': datetime.datetime(2017, 10, 10, 12, 15, 3)
    }

    d = ShamDixel(tags=tags)
    logging.debug(d)

    assert (expected_meta.items() <= d.meta.items())

    assert (d.meta["ShamName"] == "NADERMAN^TRACY^E")
    assert (d.ShamStudyDate() == "20171010")
    assert (
        d.meta["ShamAccessionNumber"] == "25d55ad283aa400af464c76d713c07ad")

    logging.debug(d.orthanc_sham_map())

    expected_replacement_map = \
        {'Replace': {'PatientName': 'NADERMAN^TRACY^E', 'PatientID': 'NTE3OTYH32XNES3LPNKR2U6CVOCZXDIL',
                     'PatientBirthDate': '19991009', 'AccessionNumber': '25d55ad283aa400af464c76d713c07ad',
                     'StudyInstanceUID': '1.2.826.0.1.3680043.10.43.62.716180617702.336145899646',
                     'StudyDate': '20171010', 'StudyTime': '121503'}, 'Keep': ['PatientSex', 'StudyDescription'],
         'Force': True}

    assert (d.orthanc_sham_map() == expected_replacement_map)

    c = Dixel(tags=tags)
    e = ShamDixel.from_dixel(c)
    assert (d.meta["ShamName"] == e.meta["ShamName"])

    f = ShamDixel.from_dixel(c, salt="FOO")
    logging.debug(f.meta["ShamName"])
    assert (d.meta["ShamName"] != f.meta["ShamName"])
Пример #11
0
def message(ctx, messenger, dixel, to_addrs, template, template_file, meta,
            dryrun):
    """Call MESSENGER SEND with data from **DIXEL.meta and **META.

     DIXEL may be a local path to an instance or a curl command to an Orthanc

     \b
     $ diana-cli message smtp path:/data/images/my_file.dcm \
       -m "{{ tags.accession_number }}" -t "*****@*****.**"
     $ diana-cli message smtp http:orthanc:orthanc@host/studies/oid... \
       -m "{{ tags.accession_number }}" -t "*****@*****.**"
    """
    services = ctx.obj.get('services')

    click.echo(click.style('Calling messenger send', underline=True,
                           bold=True))

    if dixel.startswith("path:"):
        D = DcmDir()
        d = D.get(dixel)
    elif dixel.startswith("http"):
        resp = requests.get(dixel)
        if resp:
            d = Dixel.from_orthanc(resp.json())
    else:
        click.echo(click.style("No such dixel {}".format(dixel), fg="red"))
        exit(1)

    if not services.get(messenger):
        click.echo(
            click.style("No such service {}".format(messenger), fg="red"))
        exit(1)

    _messenger = SmtpMessenger(**services[messenger])

    _messenger.send(d, to_addrs=to_addrs, msg_t=template, dryrun=dryrun)

    if dryrun:
        click.echo(messenger.get(d, to_addrs=to_addrs, msg_t=template))
Пример #12
0
data_dir = "/Users/derek/Desktop/"
data_fn = "lscr_111218-050919.csv"
fpi = os.path.join( data_dir, data_fn )
fpo = os.path.join( data_dir, "lscr_out.csv")

worklist = CsvFile(fp=fpi)
worklist.read()

results = []

for d in worklist.dixels:

    logging.debug(d)

    study = Dixel.from_montage_csv(d.tags)

    logging.debug(study)

    # qdict = {"q": item.meta['Exam Unique ID'],
    #          "modality": 4,
    #          "exam_type": [8274, 8903],  # 8119, 1997 (short term f/u)
    #          "start_date": "2018-01-01",
    #          "end_date": "2018-12-31"}
    #
    # results = M.find(qdict)
    #
    # if results:
    #
    #     study = results.pop()
Пример #13
0
    def put(self,
            dx: Dixel,
            link_duplicates=False,
            ignore_errors=True,
            **kwargs):
        if dx.dlvl > DLv.INSTANCE:
            if ignore_errors:
                print("Can only register instances")
                return
            raise ValueError("Can only register instances")
        if dx.dhash is None:
            if ignore_errors:
                print("Can only register hashed dixels")
                return
            raise AttributeError("Can only register hashed dixels")
        _uid = self.hashes.get(dx.mhash)
        if _uid is None:
            _uid = uuid.uuid4().hex
            self.meta[_uid] = dx.summary()
            self.hashes[dx.mhash] = _uid
            self.needs_cached = True  # Flag a cache update
        if self.hashes.get(dx.dhash) is None:
            self.hashes[dx.dhash] = _uid
        elif self.hashes.get(dx.dhash) != _uid:
            if link_duplicates:
                _uid1 = self.hashes.get(dx.dhash)
                self.links[_uid1].add(_uid)
                self.links[_uid].add(_uid1)
            if ignore_errors:
                print("Already seen this dhash under a different UID")
                return
            else:
                raise ValueError(
                    "Already seen this dhash under a different UID")
        if dx.bhash is not None:
            if self.hashes.get(dx.bhash) is None:
                self.hashes[dx.bhash] = _uid
            elif self.hashes.get(dx.bhash) != _uid:
                # This is always very bad!
                raise ValueError(
                    "Already seen this bhash under a different UID")

        def register_parent(par):
            _uid = self.hashes.get(par.mhash)
            if _uid is None:
                _uid = uuid.uuid4().hex
                self.meta[_uid] = par.summary()
                self.hashes[par.mhash] = _uid
                # self.hashes[par.uhash] = _uid
            cur_dhash = self.meta[_uid]["dhash"] or ""
            if cur_dhash in self.hashes:
                del (self.hashes[cur_dhash])
            new_dhash = hex_xor(dx.dhash, cur_dhash)
            self.meta[_uid]["dhash"] = new_dhash
            self.hashes[new_dhash] = _uid
            if dx.bhash is not None:
                cur_bhash = self.meta[_uid]["bhash"] or ""
                if cur_bhash in self.hashes:
                    del (self.hashes[cur_bhash])
                new_bhash = hex_xor(dx.bhash, cur_bhash)
                self.meta[_uid]["bhash"] = new_bhash
                self.hashes[new_bhash] = _uid

        ser = Dixel.from_child(dx, dlvl=DLv.SERIES)
        register_parent(ser)

        stu = Dixel.from_child(dx, dlvl=DLv.STUDY)
        register_parent(stu)
Пример #14
0
def parse_results(results, fn):
    for i, entry in enumerate(results):
        record_i = Dixel.from_montage_json(entry)
        record_i.report.anonymize()
        record_i.to_csv("{}/{}.csv".format(directory, fn[:-5]))