Skip to content

Commit

Permalink
Added diana-cli using Click library
Browse files Browse the repository at this point in the history
  • Loading branch information
derek committed Nov 30, 2018
1 parent 45347fd commit 5553265
Show file tree
Hide file tree
Showing 29 changed files with 542 additions and 98 deletions.
Empty file added apps/__init__.py
Empty file.
94 changes: 94 additions & 0 deletions apps/cli.old/README.md
@@ -0,0 +1,94 @@
DIANA CLI Applications
================

Derek Merck
<derek_merck@brown.edu>
Rhode Island Hospital and Brown University
Providence, RI

## `diana-lookup.py`

Wrapper command-line tool for a _Splunk_ query.

```
> python3 diana-lookup.py --query "index=dose_report" -e "-1d" -l now -i my_splunk -s secrets.yml
```

`secrets.yml` must have a section called "my_splunk" with keys suitable for creating
an Splunk instance that can accept the query.


## `diana-pull.py`

Wrapper command-line tool for an _Orthanc Proxy_ retrieve from modality.

```
> python3 pull-it.py -accession XYZ -series "thin * brain -p my_proxy -d my_pacs -s secrets.yml
```

`secrets.yml` must have a section called "my_proxy" with keys suitable for creating
an Orthanc instance that knows about the remote "my_pacs".


## `diana-star.py`

Wrapper command-line tool to stand up a DIANA distributed worker node for pipeline data processing.


## `diana-watcher.py`

Wrapper command-line tool to stand up a DIANA watcher daemon. Can be configured with environment vars for remote/embedded deployment, a yml/json routing file, or a directory of python routes.


## `halibut.py`

Wrapper for Halibut machine learning module (use weights in `tests`).


## `dcm2im.py`

Wrapper command-line tool to convert pixels from a DICOM format file or directory
into a standard image format (png, jpg).

```
> python dcm2py.py -i im000001.dcm
```

## `index-it.py`

Wrapper command-line tool for pre-index caching and restoring.

```
$ python3 index-it.py --location /my_path --redis_service my_redis -s secrets.yml
$ python3 index-it.py -l /my_path -r my_redis -s secrets.yml restore --an abcxyz123 -d orthanc
```

`secrets.yml` must have a section called "my_redis" with keys suitable for creating
a Redis instance.

No python3 on a system that needs re-indexed? Docker to the rescue...

```
$ docker run -v /orthanc/db:/orthanc/db -it derekmerck/diana /bin/bash
# scp server:/secrets.yml .
# python3 apps/cli/index-it.py -l /orthanc/db -r redis -s secrets.yml index -w orthanc
```

## `monitor-dose.py`

monitor-dose
Merck, Summer 2018

Wrapper to configure and run a DoseReportHarvester daemon.

```
$ python3 dose-monitor -q "gepacs" -j "dose_reports"
```



License
-------------

[MIT](http://opensource.org/licenses/mit-license.html)
File renamed without changes.
File renamed without changes.
Expand Up @@ -16,7 +16,7 @@
$ docker run -v /orthanc/db:/orthanc/db -it derekmerck/diana:amd64 /bin/bash
# scp server:/secrets.yml .
# python3 apps/cli/index-it.py -l /orthanc/db -r redis -s /secrets.yml index -w orthanc
# python3 apps/cli.old/index-it.py -l /orthanc/db -r redis -s /secrets.yml index -w orthanc
...
"""
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
97 changes: 17 additions & 80 deletions apps/cli/README.md
@@ -1,94 +1,31 @@
DIANA CLI Applications
================
# DIANA CLI

Derek Merck
<derek_merck@brown.edu>
Rhode Island Hospital and Brown University
Providence, RI

## `diana-lookup.py`
Derek Merck

Wrapper command-line tool for a _Splunk_ query.
## DICOM Analytics and Archive (DIANA)

```
> python3 diana-lookup.py --query "index=dose_report" -e "-1d" -l now -i my_splunk -s secrets.yml
```
1. `diana` -- Pythonic API for DICOM-related systems and data types
2. `diana-cli` -- CLI wrapper for invoking tasks and daemons
3. `diana-stack` -- Docker swarm definitions for DICOM-service stacks
4. `diana-embedded` -- Balena compose definitions and images for embedded DICOM services
5. `trialist` -- A Flack front end for a diana-stack supporting multiple image registries
5. `radcatr` -- TCL UI for RADCAT report review

`secrets.yml` must have a section called "my_splunk" with keys suitable for creating
an Splunk instance that can accept the query.

## The DIANA Command Line

## `diana-pull.py`
### Proxied Pull by Accession Number

Wrapper command-line tool for an _Orthanc Proxy_ retrieve from modality.
Batch:

```
> python3 pull-it.py -accession XYZ -series "thin * brain -p my_proxy -d my_pacs -s secrets.yml
```
$ `DIANA pull --accession_number 12345 --anonymize my_orthanc pacs`

`secrets.yml` must have a section called "my_proxy" with keys suitable for creating
an Orthanc instance that knows about the remote "my_pacs".
Batch:

$ `DIANA pull --file accesions.txt --anonymize my_orthanc pacs`

## `diana-star.py`

Wrapper command-line tool to stand up a DIANA distributed worker node for pipeline data processing.
### Mock Scanner Daemon

$ `DIANA mock --rate 10 my_orthanc`

## `diana-watcher.py`

Wrapper command-line tool to stand up a DIANA watcher daemon. Can be configured with environment vars for remote/embedded deployment, a yml/json routing file, or a directory of python routes.


## `halibut.py`

Wrapper for Halibut machine learning module (use weights in `tests`).


## `dcm2im.py`

Wrapper command-line tool to convert pixels from a DICOM format file or directory
into a standard image format (png, jpg).

```
> python dcm2py.py -i im000001.dcm
```

## `index-it.py`

Wrapper command-line tool for pre-index caching and restoring.

```
$ python3 index-it.py --location /my_path --redis_service my_redis -s secrets.yml
$ python3 index-it.py -l /my_path -r my_redis -s secrets.yml restore --an abcxyz123 -d orthanc
```

`secrets.yml` must have a section called "my_redis" with keys suitable for creating
a Redis instance.

No python3 on a system that needs re-indexed? Docker to the rescue...

```
$ docker run -v /orthanc/db:/orthanc/db -it derekmerck/diana /bin/bash
# scp server:/secrets.yml .
# python3 apps/cli/index-it.py -l /orthanc/db -r redis -s secrets.yml index -w orthanc
```

## `monitor-dose.py`

monitor-dose
Merck, Summer 2018

Wrapper to configure and run a DoseReportHarvester daemon.

```
$ python3 dose-monitor -q "gepacs" -j "dose_reports"
```



License
-------------

[MIT](http://opensource.org/licenses/mit-license.html)
Empty file added apps/cli/commands/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions apps/cli/commands/hello.py
@@ -0,0 +1,12 @@
import click


@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
"""Greets NAME for a total of COUNT times."""
click.echo(hello.__doc__)

for x in range(count):
click.echo('Hello %s!' % name)
58 changes: 58 additions & 0 deletions apps/cli/commands/mock.py
@@ -0,0 +1,58 @@
import random, logging, time
from datetime import datetime
import click
import attr
from diana.apis import Orthanc
from diana.mock import MockStudy

@attr.s
class MockScanner(object):
seed = attr.ib( default=None )
name = attr.ib( type=str, default="Mock Scanner" )
modality = attr.ib( type=str, default="CT" )
rate = attr.ib( type=float, default=10, converter=float )

@seed.validator
def set_seed(self, attribute, value):
if value:
random.seed(value)

def gen_study(self):
s = MockStudy(seed=self.seed,
study_datetime=datetime.now(),
station_name = self.name,
modality=self.modality )
return s

def run(self, dest: Orthanc):

while True:

logging.info("Generating mock study")
s = self.gen_study()

for d in s.dixels():
# logging.debug(d)
d.gen_file()
dest.put( d )

ave_delay = 60*60/self.rate
this_delay = random.gauss(ave_delay, ave_delay*0.3)
if this_delay < 0.1:
this_delay = 0.1
logging.info("Waiting {} secs".format(this_delay))
time.sleep( this_delay )


@click.command()
@click.argument('destination')
@click.argument('rate')
@click.pass_context
def mock(ctx, destination, rate):
"""Generate mock studies at RATE per hour and submit to DESTINATION"""
click.echo(mock.__doc__)
services = ctx.obj['SERVICES']

D = Orthanc(**services.get(destination))
M = MockScanner(name = "Diana Mock", rate = rate)
M.run(dest=D)
97 changes: 97 additions & 0 deletions apps/cli/commands/orthanc.py
@@ -0,0 +1,97 @@
import logging
from hashlib import md5
import click
import yaml
from diana.apis import Orthanc, DicomFile
from diana.utils.dicom import DicomLevel


# TODO: Query should always include StudyDate, StudyTime, AccessionNumber
# Without StudyDate/StudyTime it fails on readback

@click.command()
@click.argument('query')
@click.argument('source')
@click.option('--domain', help="Domain for proxied query")
@click.option('-r', '--retrieve', default=False)
@click.pass_context
def ofind(ctx, query, source, domain, retrieve):
"""Find items matching json QUERY in SOURCE Orthanc service {optionally with proxy DOMAIN}"""
click.echo(ofind.__doc__)
services = ctx.obj['SERVICES']

S = Orthanc(**services.get(source))
if isinstance(query, str):
query = yaml.safe_load(query)
click.echo(S.find(query, DicomLevel.STUDIES, domain, retrieve=retrieve))


@click.command()
@click.option("--accession_number", required=False)
@click.option("--worklist", type=click.File(), required=False)
@click.argument('source')
@click.argument('domain')
@click.argument('destination', type=click.Path(), required=False)
@click.option('-a', '--anonymize', default=False, is_flag=True)
@click.pass_context
def pull(ctx, accession_number, worklist, source, domain, destination, anonymize):
"""Pull items matching QUERY from SOURCE Orthanc service with proxy DOMAIN"""
click.echo(pull.__doc__)
services = ctx.obj['SERVICES']

S = Orthanc(**services.get(source))
# ep.clear()
click.echo(S)

D = None
if destination:
D = DicomFile(location=destination)

def get_by_accession_num(accession_num):

# Only if it's a file destination...
if destination:
# If dest is DicomFile
if D.check("{}.zip".format(md5(accession_num.encode('UTF8')).hexdigest())):
click.echo("Skipping {}".format(accession_num))
return

q = {
"AccessionNumber": accession_num,
"StudyDate": "",
"StudyTime": "",
"PatientName": "",
"PatientBirthDate": "",
"PatientSex": ""
}
dixels = S.find(q, DicomLevel.STUDIES, domain, retrieve=True)
click.echo(dixels)

for dixel in dixels:
if anonymize:
dixel = S.anonymize(dixel, remove=True)
click.echo(dixel)

if destination:
# If dest is DicomFile
dixel = S.get(dixel, view='archive')
logging.debug(dixel.meta['AccessionNumber'])
D.put(dixel, fn_from="AccessionNumber")
S.remove(dixel)

# If dest is peer or modality node
# ep.send(dixel, peer_dest=destination)
# ep.remove(dixel)

if not worklist and not accession_number:
logging.warning("No --accession_number or --worklist option provided. Nothing to do.")

if accession_number:
get_by_accession_num(accession_number)
return

if worklist:
for accession_num in worklist.readlines():
# Get rid of trailing return
get_by_accession_num(accession_num.rstrip())

0 comments on commit 5553265

Please sign in to comment.