diff --git a/.gitignore b/.gitignore index f136891..952a3c8 100644 --- a/.gitignore +++ b/.gitignore @@ -88,4 +88,6 @@ demo/img_ROIs_classes.zip masks src/napari_annotatorj/models/* src/napari_annotatorj/unet/__pycache__/ -src/napari_annotatorj/default_models/* \ No newline at end of file +src/napari_annotatorj/default_models/* +src/napari_annotatorj/pyinstaller/ +src/napari_annotatorj/build/ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index efe1815..d1a68ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,4 +3,13 @@ requires = ["setuptools", "wheel", "setuptools_scm"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] -write_to = "src/napari_annotatorj/_version.py" \ No newline at end of file +write_to = "src/napari_annotatorj/_version.py" + +[tool.pytest.ini_options] +filterwarnings = [ + "error", + "ignore::DeprecationWarning", + # note the use of single quote below to denote "raw" strings in TOML + 'ignore:function ham\(\) is deprecated:DeprecationWarning', + "ignore::FutureWarning", +] \ No newline at end of file diff --git a/src/napari_annotatorj/_dock_widget.py b/src/napari_annotatorj/_dock_widget.py index 2e23bdb..2401363 100644 --- a/src/napari_annotatorj/_dock_widget.py +++ b/src/napari_annotatorj/_dock_widget.py @@ -128,7 +128,7 @@ def __init__(self, napari_viewer): self.curPredictionImage=None self.curPredictionImageName=None self.curOrigImage=None - self.allowContAssistBbox=True + self.allowContAssistBbox=False #True # '0' is the default GPU if any, otherwise fall back to cpu # valid values are: ['cpu','0','1','2',...] self.gpuSetting='cpu' @@ -461,6 +461,8 @@ def openNew(self): if os.path.exists(self.test_image): img=skimage.io.imread(self.test_image) print('Test image read successfully') + self.defDir=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),'demo/') + self.defFile='img.png' else: print('Test image could not be found') else: @@ -837,6 +839,8 @@ def finishOpenNewInit(self,initSrc=None): roiLayer.mode='add_polygon' if self.freeHandROIvis not in roiLayer.mouse_drag_callbacks: roiLayer.mouse_drag_callbacks.append(self.freeHandROIvis) + + self.initShapeControls() # chkbx setting moved to its own fcn initChkBoxes @@ -881,6 +885,8 @@ def finishOpenNewInit(self,initSrc=None): if self.freeHandROIvis in roiLayer.mouse_drag_callbacks: roiLayer.mouse_drag_callbacks.remove(self.freeHandROIvis) + self.initShapeControls() + # chkbx setting moved to its own fcn initChkBoxes @@ -1083,6 +1089,7 @@ def loadROIs(self): self.bindKeys(roiLayer) + ''' def loadROIs2(self): # temporarily load a test ImageJ ROI.zip file with contours created in ImageJ and saved with AnnotatorJ # later this will start a browser dialog to select the annotation file @@ -1117,6 +1124,7 @@ def loadROIs2(self): #shapesLayer.mode = 'select' # select the "add polygon" mode from the controls by default to enable freehand ROI drawing shapesLayer.mode = 'add_polygon' + ''' def initRoiManager(self): # the rois will be stored in this object as in ImageJ's RoiManager @@ -1397,7 +1405,10 @@ def importROIsFromMaskImage(self,mask,layerName='ROI'): roiLayer=self.findROIlayer(layerName=layerName) if roiLayer is not None: roiLayer.add_polygons(shapes,edge_width=self.annotEdgeWidth,edge_color=roiColours,face_color=[0,0,0,0]) - roiLayer.properties=roiProps + #roiLayer.properties=roiProps + numpy.append(roiLayer.properties['class'],numpy.array(roiProps['class'])) + numpy.append(roiLayer.properties['name'],roiProps['name']) + numpy.append(roiLayer.properties['nameInt'],numpy.array(roiProps['nameInt'])) else: print(f'Cannot find the ROI layer') @@ -3585,12 +3596,7 @@ def updateControls(self,layerName): from napari._qt.layer_controls.qt_shapes_controls import QtShapesControls #shapeControls=None - if self.shapeControls is None: - for c in self.viewer.window._qt_viewer.controls.children(): - if isinstance(c,QtShapesControls) and c.layer.name==layerName: - self.shapeControls=c - self.shapeControls.widthSlider.valueChanged.connect(self.updateEdgeWidths) - break + self.initShapeControls(layerName=layerName) if self.shapeControls is None: print('Could not find the shapeControls') return @@ -3599,6 +3605,31 @@ def updateControls(self,layerName): self.shapeControls.widthSlider.setValue(self.annotEdgeWidth) + def initShapeControls(self,layerName='ROI',reinit=False): + from napari._qt.layer_controls.qt_shapes_controls import QtShapesControls + #if self.shapeControls is None or reinit: + if self.shapeControls is not None: + # check if valid + try: + self.shapeControls.widthSlider.name() + self.doInitShapeControls(layerName) + except Exception as e: + # does not exist + self.doInitShapeControls(layerName) + else: + self.doInitShapeControls(layerName) + + + def doInitShapeControls(self,layerName='ROI'): + from napari._qt.layer_controls.qt_shapes_controls import QtShapesControls + for c in self.viewer.window._qt_viewer.controls.children(): + if isinstance(c,QtShapesControls) and c.layer.name==layerName: + self.shapeControls=c + self.shapeControls.widthSlider.valueChanged.connect(self.updateEdgeWidths) + print(f'---- reinited shape controls to layer {layerName}') + break + + def updateEdgeWidths(self,value): roiLayer=self.findROIlayer() for i in range(roiLayer.nshapes): @@ -8587,6 +8618,7 @@ def setAnnotType(self): if self.annotatorjObj.freeHandROIvis not in roiLayer.mouse_drag_callbacks: roiLayer.mouse_drag_callbacks.append(self.annotatorjObj.freeHandROIvis) self.annotatorjObj.viewer.layers.selection.add(roiLayer) + self.annotatorjObj.initShapeControls(reinit=True) elif newAnnotType=='bbox': roiLayer=self.annotatorjObj.findROIlayer() if roiLayer is None: @@ -8596,6 +8628,7 @@ def setAnnotType(self): if self.annotatorjObj.freeHandROIvis in roiLayer.mouse_drag_callbacks: roiLayer.mouse_drag_callbacks.remove(self.annotatorjObj.freeHandROIvis) self.annotatorjObj.viewer.layers.selection.add(roiLayer) + self.annotatorjObj.initShapeControls(reinit=True) elif newAnnotType=='semantic': labelLayer=self.annotatorjObj.findLabelsLayerName(layerName='semantic') if labelLayer is None: @@ -10472,10 +10505,11 @@ def initFileList(annotatorjObj,callback): fileList=QListWidget() fileList.setSelectionMode(QAbstractItemView.SingleSelection) - for fi,f in enumerate(annotatorjObj.curFileList): - fileList.insertItem(fi,f) + if annotatorjObj.curFileList is not None: + for fi,f in enumerate(annotatorjObj.curFileList): + fileList.insertItem(fi,f) - fileList.setCurrentItem(fileList.item(annotatorjObj.curFileIdx)) + fileList.setCurrentItem(fileList.item(annotatorjObj.curFileIdx)) fileList.currentItemChanged.connect(callback) diff --git a/src/napari_annotatorj/_tests/mimic_roiloading_frommask.py b/src/napari_annotatorj/_tests/mimic_roiloading_frommask.py new file mode 100644 index 0000000..7bf5a8d --- /dev/null +++ b/src/napari_annotatorj/_tests/mimic_roiloading_frommask.py @@ -0,0 +1,38 @@ +import napari +from _dock_widget import AnnotatorJ +from roifile import ImagejRoi +from napari.layers import Shapes, Image, Labels, Layer + +def countLayers(layers,layerType=Image): + c=0 + for x in layers: + if (x.__class__ is layerType): + c+=1 + return c + +viewer = napari.Viewer() +pluginInstance=AnnotatorJ(viewer) +viewer.window.add_dock_widget(pluginInstance,name='AnnotatorJ') + +tmp_path="C:/work/szbk/annotatorj/testing_napari/dummying_nucleus/masks_demo/labelled_masks/" +pluginInstance.defFile='full adj.tiff' +# load dummy roi +rois=ImagejRoi.fromfile(pluginInstance.test_rois) +shapesLayer=pluginInstance.extractROIdata(rois) +n=len(shapesLayer.data) +print(f'numshapes: {n}') +# add 2nd shapes layer with these rois +pluginInstance.viewer.add_layer(shapesLayer) +prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) +print(f'before: {prev_count_shapes}') +#''' +pluginInstance.viewer.layers.remove('ROI') +print(f'is 2?: {countLayers(pluginInstance.viewer.layers,layerType=Shapes)}') +pluginInstance.loadRoisFromMask(tmp_path,False) +after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) +print(f'after: {after_count_shapes}') +newShapesLayer=pluginInstance.viewer.layers[-1] +print(f'new shapes num: {len(newShapesLayer.data)}') +#''' + +napari.run() \ No newline at end of file diff --git a/src/napari_annotatorj/_tests/mimic_roiloading_fromtext.py b/src/napari_annotatorj/_tests/mimic_roiloading_fromtext.py new file mode 100644 index 0000000..8b200f3 --- /dev/null +++ b/src/napari_annotatorj/_tests/mimic_roiloading_fromtext.py @@ -0,0 +1,84 @@ +import napari +from _dock_widget import AnnotatorJ,ExportFrame +from roifile import ImagejRoi +from napari.layers import Shapes, Image, Labels, Layer +import os,sys + +def countLayers(layers,layerType=Image): + c=0 + for x in layers: + if (x.__class__ is layerType): + c+=1 + return c + +viewer = napari.Viewer() +pluginInstance=AnnotatorJ(viewer) +viewer.window.add_dock_widget(pluginInstance,name='AnnotatorJ') + +tmp_path="C:/work/szbk/annotatorj/testing_napari/dummying_nucleus/coording/" +pluginInstance.defFile='dummy_mask.tiff' +filename='dummy_mask.csv' +filename2='dummy_mask.txt' +# load dummy roi +rois=ImagejRoi.fromfile(pluginInstance.test_rois) +shapesLayer=pluginInstance.extractROIdata(rois) +n=len(shapesLayer.data) +print(f'numshapes: {n}') +prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) +print(f'before: {prev_count_shapes}') + +if os.path.isfile(os.path.join(tmp_path,filename)): + os.remove(os.path.join(tmp_path,filename)) +if os.path.isfile(os.path.join(tmp_path,filename2)): + os.remove(os.path.join(tmp_path,filename2)) + +pluginInstance.ExportFrame=ExportFrame(pluginInstance.viewer,annotatorjObj=pluginInstance) +bboxes=pluginInstance.ExportFrame.fillBboxList(shapesLayer,len(shapesLayer.data)) +pluginInstance.ExportFrame.saveExportedCSV(bboxes,os.path.join(tmp_path,filename)) +success=os.path.isfile(os.path.join(tmp_path,filename)) +print(f'wrote file: {success}') + +pluginInstance.loadRoisFromCoords(tmp_path) +print(f'loaded: {countLayers(pluginInstance.viewer.layers,layerType=Shapes)}') +#''' +after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) +print(f'after: {after_count_shapes}') +newShapesLayer=pluginInstance.viewer.layers[-1] +print(f'new shapes num: {len(newShapesLayer.data)}') +#''' + +# remove it +print(f'layers before REMOVE: {pluginInstance.viewer.layers}') +pluginInstance.viewer.layers.remove('ROI') +print(f'--------------- REMOVING ROI LAYER -----------------') +print(f'after remove layer count: {countLayers(pluginInstance.viewer.layers,layerType=Shapes)}') +print(f'layers after REMOVE: {pluginInstance.viewer.layers}') +os.remove(os.path.join(tmp_path,filename)) + +# write yolo file +bboxes=pluginInstance.ExportFrame.fillBboxListYOLO(shapesLayer,n,256,256) +pluginInstance.ExportFrame.saveExportedCSVyolo(bboxes,os.path.join(tmp_path,filename2)) +success=os.path.isfile(os.path.join(tmp_path,filename2)) +print(f'wrote second file: {success}') + +pluginInstance.loadRoisFromCoords(tmp_path) +print(f'loaded 2nd: {countLayers(pluginInstance.viewer.layers,layerType=Shapes)}') +newShapesLayer=pluginInstance.viewer.layers[-1] +print(f'numshapes 2nd: {len(newShapesLayer.data)}') + +pluginInstance.testMode=True +pluginInstance.openNew() +print(f'opened image') +pluginInstance.testMode=False +pluginInstance.defFile='dummy_mask.tiff' +pluginInstance.loadRoisFromCoords(tmp_path) +print(f'loaded 3rd: {countLayers(pluginInstance.viewer.layers,layerType=Shapes)}') +newShapesLayer=pluginInstance.viewer.layers[-1] +print(f'numshapes 3rd: {len(newShapesLayer.data)}') + + +napari.run() + + + + diff --git a/src/napari_annotatorj/_tests/test_annotatorj_fcns.py b/src/napari_annotatorj/_tests/test_annotatorj_fcns.py new file mode 100644 index 0000000..d41240b --- /dev/null +++ b/src/napari_annotatorj/_tests/test_annotatorj_fcns.py @@ -0,0 +1,677 @@ +import napari_annotatorj +import pytest +#from .test_dock_widget import im_layer_dims +from napari.layers import Shapes, Image, Labels, Layer +from napari.layers.shapes import _shapes_mouse_bindings as mouse_bindings +from roifile import ImagejRoi +import cv2 +import numpy +from qtpy.QtCore import Qt,QTimer,QPoint +from qtpy.QtWidgets import QApplication +from time import sleep +import os + +# helper fcns +def countLayers(layers,layerType=Image): + c=0 + for x in layers: + if (x.__class__ is layerType): + c+=1 + return c + +@pytest.fixture +def startAnnotatorJwidget(qtbot,make_napari_viewer): + def doStart(): + viewer = make_napari_viewer() + pluginInstance=napari_annotatorj.AnnotatorJ(viewer) + qtbot.addWidget(pluginInstance) + return pluginInstance + return doStart + +def im_layer(dims): + # dims is like (100,100,3) + return Image(numpy.array(numpy.random.random(dims)),name='Image') + +@pytest.fixture +def write_test_file(tmp_path): + def write(filename): + # tmp_path is a pytest fixture, it will be cleaned up when the tests are run + testFile=str(tmp_path/filename) + testData=numpy.random.random((20,20)) + cv2.imwrite(testFile,testData) + + return testFile,testData + + return write + +def init_annotatorj_w_image(startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + dims=(100,100,3) + dummy_image=im_layer(dims) + imageLayer = pluginInstance.viewer.add_layer(dummy_image) + # mock a name so init fcn doesn't fail + pluginInstance.destNameRaw='dummy' + return pluginInstance + +@pytest.fixture +def save_roi_2_mask(tmp_path): + def write_mask(filename,shapesLayer,w,h): + labels=shapesLayer.to_labels([w,h]) + outputFileName=os.path.join(tmp_path,filename) + napari_annotatorj.ExportFrame.saveExportedImage(labels,outputFileName) + return outputFileName,labels + return write_mask + +@pytest.fixture +def save_roi_2_text(tmp_path): + def write_text(filename,exporter,shapesLayer,format=0,w=256,h=256): + if format==0: + bboxes=exporter.fillBboxList(shapesLayer,len(shapesLayer.data)) + exporter.saveExportedCSV(bboxes,os.path.join(tmp_path,filename)) + return True + elif format==1: + bboxes=exporter.fillBboxListYOLO(shapesLayer,len(shapesLayer.data),w,h) + exporter.saveExportedCSVyolo(bboxes,os.path.join(tmp_path,filename)) + return True + else: + return False + return write_text + + +def test_reset_annot_type(startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + # reset annot type + pluginInstance.selectedAnnotationType='instance' + pluginInstance.writeParams2File() + +def test_init_fcns(startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + pluginInstance.preInitChkBxs() + assert pluginInstance.contAssist==False, f'expected contAssist to be {False} but was {pluginInstance.contAssist}' + assert pluginInstance.editMode==False, f'expected editMode to be {False} but was {pluginInstance.editMode}' + + pluginInstance.initChkBoxes() + assert pluginInstance.selectedAnnotationType=='instance',f'expected selectedAnnotationType to be instance but was {pluginInstance.selectedAnnotationType}' + assert pluginInstance.classMode==False, f'expected classMode to be {False} but was {pluginInstance.classMode}' + assert pluginInstance.addAuto==False, f'expected addAuto to be {False} but was {pluginInstance.addAuto}' + assert pluginInstance.chkEdit.isEnabled()==True,f'expected chkEdit enabled to be {True} but was {pluginInstance.chkEdit.isEnabled()}' + assert pluginInstance.chckbxContourAssist.isEnabled()==True,f'expected chckbxContourAssist enabled to be {True} but was {pluginInstance.chckbxContourAssist.isEnabled()}' + assert pluginInstance.chckbxClass.isEnabled()==True,f'expected chckbxClass enabled to be {True} but was {pluginInstance.chckbxClass.isEnabled()}' + + print('init fcns tested') + + +def test_opening_image_randgen(tmp_path,startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + sleep(1) + # fake a 100x100x3 random rgb image + dims=(100,100,3) + expected_width=0.5 + dummy_image=im_layer(dims) + prev_count_im=countLayers(pluginInstance.viewer.layers) + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + # mock openNew fcn + imageLayer = pluginInstance.viewer.add_layer(dummy_image) + after_count_im=countLayers(pluginInstance.viewer.layers) + assert after_count_im==prev_count_im+1,f'expected image layer count after image addition to be {prev_count_im+1} but was {after_count_im}' + assert prev_count_shapes==1,f'expected shapes layer count to be 1 but was {prev_count_shapes}' + s=imageLayer.data.shape + assert s[0]==dims[0],f'expected image to have dim1 dims[0] but was {s[0]}' + assert s[1]==dims[1],f'expected image to have dim1 dims[1] but was {s[1]}' + assert s[2]==dims[2],f'expected image to have dim1 dims[2] but was {s[2]}' + pluginInstance.imgSize=s + assert pluginInstance.imgSize==imageLayer.data.shape,f'unexpected image layer size' + pluginInstance.defDir=tmp_path + pluginInstance.defFile='dummy_name.png' + pluginInstance.curPredictionImageName=pluginInstance.defFile + pluginInstance.curPredictionImage=None + pluginInstance.curOrigImage=None + + pluginInstance.finishOpenNewInit() + assert pluginInstance.findROIlayer is not None,f'expected to find at least 1 ROI layer' + # the correct annotEdgeWidth for a <300 x <300 image is 0.5 + assert pluginInstance.annotEdgeWidth==expected_width,f'expected annotEdgeWidth to be {expected_width} but was {pluginInstance.annotEdgeWidth}' + assert pluginInstance.started==True,f'expected started to be True but was {pluginInstance.started}' + + +def test_opening_image_randgen_size(tmp_path,startAnnotatorJwidget): + sizes=[(100,100,3), + (300,300,3),(400,400,3),(500,500,3),(700,700,3), + (1000,1000,3),(1200,1200,3),(1500,1500,3),(1800,1800,3), + (2000,2000,3),(2300,2300,3),(3000,3000,3),(3100,3100,3)] + expected_width=[0.5, + 0.5,1.0,1.0,1.5, + 1.5,2.0,2.0,3.0, + 3.0,5.0,5.0,7.0] + pluginInstance=startAnnotatorJwidget() + for s,e in zip(sizes,expected_width): + # fake a random rgb image + dims=s + expected_width=e + dummy_image=im_layer(dims) + imageLayer = pluginInstance.viewer.add_layer(dummy_image) + s=imageLayer.data.shape + assert s[0]==dims[0],f'expected image to have dim1 dims[0] but was {s[0]}' + assert s[1]==dims[1],f'expected image to have dim1 dims[1] but was {s[1]}' + assert s[2]==dims[2],f'expected image to have dim1 dims[2] but was {s[2]}' + pluginInstance.imgSize=s + assert pluginInstance.imgSize==imageLayer.data.shape,f'unexpected image layer size' + pluginInstance.defDir=tmp_path + pluginInstance.defFile='dummy_name.png' + pluginInstance.curPredictionImageName=pluginInstance.defFile + pluginInstance.curPredictionImage=None + pluginInstance.curOrigImage=None + + pluginInstance.finishOpenNewInit() + assert pluginInstance.findROIlayer is not None,f'expected to find at least 1 ROI layer' + assert pluginInstance.annotEdgeWidth==expected_width,f'expected annotEdgeWidth to be {expected_width} but was {pluginInstance.annotEdgeWidth}' + + +def test_opening_image_randgen_semantic(tmp_path,startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + sleep(1) + pluginInstance.selectedAnnotationType='semantic' + # fake a 100x100x3 random rgb image + dims=(100,100,3) + expected_width=0.5 + dummy_image=im_layer(dims) + prev_count_im=countLayers(pluginInstance.viewer.layers) + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + # mock openNew fcn + imageLayer = pluginInstance.viewer.add_layer(dummy_image) + after_count_im=countLayers(pluginInstance.viewer.layers) + assert after_count_im==prev_count_im+1,f'expected image layer count after image addition to be {prev_count_im+1} but was {after_count_im}' + assert prev_count_shapes==1,f'expected shapes layer count to be 1 but was {prev_count_shapes}' + s=imageLayer.data.shape + pluginInstance.imgSize=s + pluginInstance.defDir=tmp_path + pluginInstance.defFile='dummy_name.png' + pluginInstance.curPredictionImageName=pluginInstance.defFile + pluginInstance.curPredictionImage=None + pluginInstance.curOrigImage=None + + pluginInstance.finishOpenNewInit() + #assert pluginInstance.findROIlayer is None,f'expected to find no ROI layer' + labelLayer=pluginInstance.findLabelsLayerName(layerName='semantic') + assert labelLayer is not None,f'expected to find semantic annotation layer' + labelSize=labelLayer.data.shape + assert labelSize[0]==s[0] and labelSize[1]==s[1] + assert labelLayer.mode=='paint' + assert labelLayer.opacity==0.5 + assert pluginInstance.started==True,f'expected started to be True but was {pluginInstance.started}' + + +def test_opening_image_randgen_bbox(tmp_path,startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + sleep(1) + pluginInstance.selectedAnnotationType='bbox' + # fake a 100x100x3 random rgb image + dims=(100,100,3) + expected_width=0.5 + dummy_image=im_layer(dims) + prev_count_im=countLayers(pluginInstance.viewer.layers) + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + # mock openNew fcn + imageLayer = pluginInstance.viewer.add_layer(dummy_image) + after_count_im=countLayers(pluginInstance.viewer.layers) + assert after_count_im==prev_count_im+1,f'expected image layer count after image addition to be {prev_count_im+1} but was {after_count_im}' + assert prev_count_shapes==1,f'expected shapes layer count to be 1 but was {prev_count_shapes}' + s=imageLayer.data.shape + pluginInstance.imgSize=s + pluginInstance.defDir=tmp_path + pluginInstance.defFile='dummy_name.png' + pluginInstance.curPredictionImageName=pluginInstance.defFile + pluginInstance.curPredictionImage=None + pluginInstance.curOrigImage=None + + pluginInstance.finishOpenNewInit() + roiLayer=pluginInstance.findROIlayer() + assert roiLayer is not None,f'expected to find at least 1 ROI layer' + assert roiLayer.mode=='add_rectangle' + assert pluginInstance.freeHandROIvis not in roiLayer.mouse_drag_callbacks + assert pluginInstance.started==True,f'expected started to be True but was {pluginInstance.started}' + + # reset annot type + pluginInstance.selectedAnnotationType='instance' + pluginInstance.writeParams2File() + +''' +def test_opening_image_real(tmp_path,qtbot,startAnnotatorJwidget,write_test_file): + pluginInstance=startAnnotatorJwidget() + file,data=write_test_file("dummy_name.png") + pluginInstance.defDir=tmp_path + pluginInstance.defFile='dummy_name.png' + prev_count_im=countLayers(pluginInstance.viewer.layers) + #qtbot.mouseClick(pluginInstance.btnOpen,Qt.LeftButton,delay=0) + + # mock clicking ok + def handle_dialog(): + #while not pluginInstance.openQfileDialog.isVisible(): + # pass + while pluginInstance.openQfileDialog is None: + pass + #qtbot.keyPress(pluginInstance.openQfileDialog,Qt.Key_Enter) + print(pluginInstance.openQfileDialog) + pluginInstance.openQfileDialog.accept() + #qtbot.keyPress(pluginInstance.openQfileDialog,Qt.Key_Enter) + print('*/*/*/*/*/*/*/ PRESSED ENTER ON DIALOG /*/*/*/*/*/*/*/*') + #openDialog=QApplication.focusWidget() + #print(openDialog.__dir__()) + #openDialog.accept() + QTimer.singleShot(500,handle_dialog) + qtbot.mouseClick(pluginInstance.btnOpen,Qt.LeftButton,delay=0) + + after_count_im=countLayers(pluginInstance.viewer.layers) + assert after_count_im==prev_count_im+1,f'expected image layer count after image addition to be {prev_count_im+1} but was {after_count_im}' +''' +def test_opening_test_image(qtbot,startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + sleep(10) + pluginInstance.testMode=True + prev_count_im=countLayers(pluginInstance.viewer.layers) + qtbot.mouseClick(pluginInstance.btnOpen,Qt.LeftButton,delay=0) + after_count_im=countLayers(pluginInstance.viewer.layers) + assert after_count_im==prev_count_im+1,f'expected image layer count after image addition to be {prev_count_im+1} but was {after_count_im}' + assert pluginInstance.curFileIdx==0 + assert pluginInstance.buttonPrev.isEnabled()==False + assert pluginInstance.buttonNext.isEnabled()==False + assert pluginInstance.lblCurrentFile.text()==" (1/1): img.png" + assert pluginInstance.selectedAnnotationType=="instance" + roiLayer=pluginInstance.findROIlayer() + assert roiLayer.mode=="add_polygon" + + +def test_opening_test_image_w_ROI(qtbot,startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + sleep(10) + pluginInstance.setTestMode(mode=True) + qtbot.mouseClick(pluginInstance.btnOpen,Qt.LeftButton,delay=0) + qtbot.mouseClick(pluginInstance.btnLoad,Qt.LeftButton,delay=0) + roiLayer=pluginInstance.findROIlayer() + assert roiLayer.mode=="add_polygon" + assert pluginInstance.loadedROI==True + assert pluginInstance.roiCount==49 + + +def test_roi_loading_from_mask(tmp_path,startAnnotatorJwidget,save_roi_2_mask): + pluginInstance=startAnnotatorJwidget() + sleep(1) + testName='dummy_mask.tiff' + pluginInstance.defFile='dummy_mask.tiff' + # load dummy roi + rois=ImagejRoi.fromfile(pluginInstance.test_rois) + shapesLayer=pluginInstance.extractROIdata(rois) + assert shapesLayer is not None + n=len(shapesLayer.data) + assert n>0 + # add 2nd shapes layer with these rois + pluginInstance.viewer.add_layer(shapesLayer) + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert prev_count_shapes==2 + filename,labelLayer=save_roi_2_mask(testName,shapesLayer,256,256) + assert countLayers(pluginInstance.viewer.layers,layerType=Shapes)==2 + pluginInstance.loadRoisFromMask(tmp_path,False) + after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert after_count_shapes==prev_count_shapes + newShapesLayer=pluginInstance.viewer.layers[-1] + assert len(newShapesLayer.data)==2*n + pluginInstance.viewer.layers.remove('ROI') + pluginInstance.loadRoisFromMask(tmp_path,False) + after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert after_count_shapes==prev_count_shapes + newShapesLayer=pluginInstance.viewer.layers[-1] + assert len(newShapesLayer.data)==n + + +def test_classified_roi_loading(qtbot,startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + # use the classified roi.zip file + pluginInstance.test_rois=pluginInstance.test_rois[:-4]+'_classes.zip' + pluginInstance.setTestMode(True) + qtbot.mouseClick(pluginInstance.btnLoad,Qt.LeftButton,delay=0) + roiLayer=pluginInstance.findROIlayer() + assert roiLayer is not None + assert len(roiLayer.data)>0 + assert pluginInstance.startedClassifying==True + + +def test_add2roimanager(tmp_path,startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + sleep(1) + # load dummy roi + rois=ImagejRoi.fromfile(pluginInstance.test_rois) + pluginInstance.add2RoiManager(rois) + assert pluginInstance.manager is not None + assert pluginInstance.roiCount>0 and pluginInstance.roiCount==len(rois) + + # init the manager too + pluginInstance.viewer.layers.remove('ROI') + pluginInstance.manager=None + pluginInstance.add2RoiManager(rois) + assert pluginInstance.manager is not None + assert pluginInstance.roiCount>0 and pluginInstance.roiCount==len(rois) + + +def test_roi_loading_from_coords(tmp_path,startAnnotatorJwidget,save_roi_2_text): + pluginInstance=startAnnotatorJwidget() + sleep(1) + testName='dummy_mask.csv' + testName2='dummy_mask.txt' + pluginInstance.defFile='dummy_mask.tiff' + # load dummy roi + rois=ImagejRoi.fromfile(pluginInstance.test_rois) + shapesLayer=pluginInstance.extractROIdata(rois) + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert shapesLayer is not None + n=len(shapesLayer.data) + assert n>0 + assert prev_count_shapes==1 + pluginInstance.testMode=False + + # start an exporter + pluginInstance.ExportFrame=napari_annotatorj.ExportFrame(pluginInstance.viewer,annotatorjObj=pluginInstance) + # default COCO coords format (absolute) + assert pluginInstance.ExportFrame.bboxFormat==0 + tmp=save_roi_2_text(testName,pluginInstance.ExportFrame,shapesLayer,0) + assert os.path.isfile(os.path.join(tmp_path,testName)) + + # see that absolute coords loading doesn't fail when no image is opened + pluginInstance.loadRoisFromCoords(tmp_path) + after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert after_count_shapes==prev_count_shapes+1 + newShapesLayer=pluginInstance.viewer.layers[-1] + assert newShapesLayer.name=='ROI' + assert len(newShapesLayer.data)==n + assert 'ROI' in pluginInstance.viewer.layers and 'ROI_prev' in pluginInstance.viewer.layers + pluginInstance.viewer.layers.remove('ROI') + assert countLayers(pluginInstance.viewer.layers,layerType=Shapes)==prev_count_shapes + + # delete the tmp file to ensure the next load doesn't attempt to load it + os.remove(os.path.join(tmp_path,testName)) + assert not os.path.isfile(os.path.join(tmp_path,testName)) + + # see that coord loading fails when no image is opened to calculate the relative coords to xy positions when saved as YOLO coords + pluginInstance.ExportFrame.bboxFormat=1 + assert pluginInstance.ExportFrame.bboxFormat==1 + tmp=save_roi_2_text(testName2,pluginInstance.ExportFrame,shapesLayer,1,256,256) # test image size + assert os.path.isfile(os.path.join(tmp_path,testName2)) + pluginInstance.loadRoisFromCoords(tmp_path) + after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert after_count_shapes==prev_count_shapes # no new loaded roi layer + + # now add the image then load the relative coords file again to succeed + pluginInstance.testMode=True + pluginInstance.openNew() + # check if the image is opened + assert countLayers(pluginInstance.viewer.layers)==1 + pluginInstance.testMode=False + pluginInstance.defFile='dummy_mask.tiff' + pluginInstance.loadRoisFromCoords(tmp_path) + after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert after_count_shapes==prev_count_shapes+2 + newShapesLayer=pluginInstance.viewer.layers[-1] + assert len(newShapesLayer.data)==n + + +def test_saveData(qtbot,tmp_path,startAnnotatorJwidget,write_test_file): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + testName='dummy.png' + pluginInstance.defDir=tmp_path + pluginInstance.defFile=testName + testFile,testData=write_test_file(testName) + assert countLayers(pluginInstance.viewer.layers)==1 + # load dummy roi + rois=ImagejRoi.fromfile(pluginInstance.test_rois) + shapesLayer=pluginInstance.extractROIdata(rois) + pluginInstance.viewer.add_layer(shapesLayer) + assert countLayers(pluginInstance.viewer.layers,layerType=Shapes)==2 + roiLayer=pluginInstance.findROIlayer() + assert len(roiLayer.data)>0 + assert pluginInstance.stepping==False + assert pluginInstance.startedClassifying==False + assert pluginInstance.started==False + assert pluginInstance.findImageLayer() is not None and pluginInstance.findImageLayer().data is not None + assert pluginInstance.findOpenedImage()==True + pluginInstance.started=True + assert pluginInstance.started==True + # save + def handle_dialog(): + # get a reference to the dialog and handle it here + while pluginInstance.classSelectionDialog is None: + pass + pluginInstance.saveClassSelectionOk() + QTimer.singleShot(500, handle_dialog) + qtbot.mouseClick(pluginInstance.btnSave,Qt.LeftButton,delay=1) + assert pluginInstance.selectedClass=='normal' + assert os.path.isfile(os.path.join(tmp_path,'normal','dummy_ROIs.zip')) + assert pluginInstance.finishedSaving==True + + + +def test_init_annotatorj_w_image(startAnnotatorJwidget): + pluginInstance=startAnnotatorJwidget() + sleep(10) + assert pluginInstance.contAssist==False + assert pluginInstance.classMode==False + assert pluginInstance.editMode==False + assert pluginInstance.chkEdit.isEnabled()==True + assert pluginInstance.chckbxClass.isEnabled()==True + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + + # add a dummy image layer + # fake a 100x100x3 random rgb image + dims=(100,100,3) + dummy_image=im_layer(dims) + prev_count_im=countLayers(pluginInstance.viewer.layers) + imageLayer = pluginInstance.viewer.add_layer(dummy_image) + after_count_im=countLayers(pluginInstance.viewer.layers) + assert after_count_im==prev_count_im+1,f'expected image layer count after image addition to be {prev_count_im+1} but was {after_count_im}' + return pluginInstance + + +# --------------- + +# test checkboxes +def test_checkbox_init_contAssist(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + pluginInstance.chckbxContourAssist.setChecked(True) + sleep(1) + assert pluginInstance.chckbxContourAssist.isChecked()==True + assert pluginInstance.contAssist==True + pluginInstance.preInitChkBxs() + assert pluginInstance.chckbxAddAutomatically.isEnabled()==False + assert pluginInstance.editMode==False + assert pluginInstance.classMode==False + assert pluginInstance.chckbxClass.isEnabled()==False + + +def test_checkbox_init_editMode(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + pluginInstance.chkEdit.setChecked(True) + sleep(1) + assert pluginInstance.chkEdit.isChecked()==True + assert pluginInstance.editMode==True + pluginInstance.preInitChkBxs() + assert pluginInstance.chckbxAddAutomatically.isEnabled()==False + assert pluginInstance.contAssist==False + assert pluginInstance.classMode==False + assert pluginInstance.chckbxClass.isEnabled()==False + assert pluginInstance.chckbxContourAssist.isEnabled()==False + + +def test_checkbox_init_classMode(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + pluginInstance.chckbxClass.setChecked(True) + sleep(1) + assert pluginInstance.chckbxClass.isChecked()==True + assert pluginInstance.classMode==True + assert pluginInstance.editMode==False + pluginInstance.initChkBoxes() + assert pluginInstance.chkEdit.isEnabled()==False + assert pluginInstance.editMode==False + assert pluginInstance.contAssist==False + assert pluginInstance.classMode==True + assert pluginInstance.chckbxContourAssist.isEnabled()==False + + +def test_checkbox_init_editMode2(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + pluginInstance.chkEdit.setChecked(True) + sleep(1) + assert pluginInstance.chkEdit.isChecked()==True + assert pluginInstance.classMode==False + assert pluginInstance.editMode==True + pluginInstance.initChkBoxes() + assert pluginInstance.chckbxClass.isEnabled()==False + assert pluginInstance.editMode==True + assert pluginInstance.contAssist==False + assert pluginInstance.classMode==False + assert pluginInstance.chckbxContourAssist.isEnabled()==False + + +def test_checkbox_init_constAssist2(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + pluginInstance.chckbxContourAssist.setChecked(True) + sleep(1) + assert pluginInstance.chckbxContourAssist.isChecked()==True + assert pluginInstance.classMode==False + assert pluginInstance.editMode==False + assert pluginInstance.contAssist==True + pluginInstance.initChkBoxes() + assert pluginInstance.chkEdit.isEnabled()==False + assert pluginInstance.editMode==False + assert pluginInstance.contAssist==True + assert pluginInstance.classMode==False + assert pluginInstance.chckbxClass.isEnabled()==False + + +def test_checkbox_init_semantic(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + pluginInstance.selectedAnnotationType='semantic' + sleep(1) + assert pluginInstance.classMode==False + assert pluginInstance.editMode==False + assert pluginInstance.contAssist==False + pluginInstance.initChkBoxes() + assert pluginInstance.chkEdit.isEnabled()==False + assert pluginInstance.editMode==False + assert pluginInstance.contAssist==False + assert pluginInstance.classMode==False + assert pluginInstance.chckbxContourAssist.isEnabled()==False + assert pluginInstance.chckbxClass.isEnabled()==False + + +def test_checkbox_init_bbox(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + pluginInstance.selectedAnnotationType='bbox' + sleep(1) + assert pluginInstance.classMode==False + assert pluginInstance.editMode==False + assert pluginInstance.contAssist==False + pluginInstance.initChkBoxes() + assert pluginInstance.chkEdit.isEnabled()==False + assert pluginInstance.editMode==False + assert pluginInstance.contAssist==False + assert pluginInstance.classMode==False + assert pluginInstance.chckbxContourAssist.isEnabled()==False + assert pluginInstance.chckbxClass.isEnabled()==True + + +def test_checkbox_contAssist(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + assert countLayers(pluginInstance.viewer.layers)==1 + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + + # start testing the checkbox + pluginInstance.chckbxContourAssist.setChecked(True) + sleep(1) + assert pluginInstance.chckbxContourAssist.isChecked()==True + assert pluginInstance.contAssist==True + assert pluginInstance.classMode==False + assert pluginInstance.editMode==False + assert pluginInstance.chkEdit.isEnabled()==False + assert pluginInstance.chckbxClass.isEnabled()==False + after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert after_count_shapes==prev_count_shapes+1,f'expected shapes layer count after contour assist init to be {prev_count_shapes+1} but was {after_count_shapes}' + contAssistLayer=pluginInstance.viewer.layers[-1] + assert contAssistLayer.name=='contourAssist' + assert contAssistLayer.mode=='add_polygon' + + # check default unchecked states + pluginInstance.chckbxContourAssist.setChecked(False) + sleep(1) + assert countLayers(pluginInstance.viewer.layers,layerType=Shapes)==prev_count_shapes + assert pluginInstance.chckbxContourAssist.isChecked()==False + assert pluginInstance.contAssist==False + + # remove image to trigger fail + pluginInstance.viewer.layers.remove('Image') + assert countLayers(pluginInstance.viewer.layers)==0 + pluginInstance.chckbxContourAssist.setChecked(True) + sleep(1) + assert pluginInstance.chckbxContourAssist.isChecked()==True + assert pluginInstance.contAssist==False + + +def test_checkbox_classMode(startAnnotatorJwidget): + pluginInstance=init_annotatorj_w_image(startAnnotatorJwidget) + assert countLayers(pluginInstance.viewer.layers)==1 + prev_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + num_dw = len(pluginInstance.viewer.window._dock_widgets) + + # start testing the checkbox + pluginInstance.chckbxClass.setChecked(True) + sleep(1) + assert pluginInstance.chckbxClass.isChecked()==True + assert pluginInstance.contAssist==False + assert pluginInstance.classMode==True + assert pluginInstance.editMode==False + assert pluginInstance.chkEdit.isEnabled()==True + assert pluginInstance.chckbxContourAssist.isEnabled()==False + after_count_shapes=countLayers(pluginInstance.viewer.layers,layerType=Shapes) + assert after_count_shapes==prev_count_shapes,f'expected shapes layer count after contour assist init to be {prev_count_shapes} but was {after_count_shapes}' + roiLayer=pluginInstance.findROIlayer() + assert roiLayer.name=='ROI' + assert roiLayer.mode=='select' + + assert len(pluginInstance.viewer.window._dock_widgets)==num_dw+1 + assert pluginInstance.classesFrame is not None,f'Failed to open Classes widget' + assert len(pluginInstance.classFrameNames)==2 + + # check default unchecked states + pluginInstance.chckbxClass.setChecked(False) + sleep(1) + assert pluginInstance.chckbxClass.isChecked()==False + assert pluginInstance.classMode==False + assert pluginInstance.chckbxContourAssist.isEnabled()==True + roiLayer=pluginInstance.findROIlayer() + assert roiLayer.mode=='add_polygon' + assert mouse_bindings.select not in roiLayer.mouse_drag_callbacks + assert pluginInstance.customShapesLayerSelect not in roiLayer.mouse_drag_callbacks + + # check semantic disabled state + pluginInstance.selectedAnnotationType='semantic' + pluginInstance.chckbxClass.setChecked(True) + sleep(1) + assert pluginInstance.chckbxClass.isChecked()==True + assert pluginInstance.classMode==False + + # reset + pluginInstance.selectedAnnotationType='instance' + pluginInstance.chckbxClass.setChecked(False) + + # remove image to trigger fail + pluginInstance.viewer.layers.remove('Image') + assert countLayers(pluginInstance.viewer.layers)==0 + pluginInstance.chckbxClass.setChecked(True) + sleep(1) + assert pluginInstance.chckbxClass.isChecked()==True + assert pluginInstance.classMode==False + + + +def test_the_fcns(startAnnotatorJwidget): + #pluginInstance=startAnnotatorJwidget() + #test_init_fcns(startAnnotatorJwidget) + #test_opening_image_randgen(startAnnotatorJwidget,im_layer) + pass \ No newline at end of file diff --git a/src/napari_annotatorj/_tests/test_dock_widget.py b/src/napari_annotatorj/_tests/test_dock_widget.py index 00d0bc9..a6c7c3b 100644 --- a/src/napari_annotatorj/_tests/test_dock_widget.py +++ b/src/napari_annotatorj/_tests/test_dock_widget.py @@ -1,11 +1,12 @@ import napari_annotatorj import pytest from time import sleep +from napari_annotatorj._dock_widget import OptionsFrame,ColourSelector,TrainWidget,HelpWidget,Q3DWidget,FileListWidget # this is your plugin name declared in your napari.plugins entry point MY_PLUGIN_NAME = "napari-annotatorj" # the name of your widget(s) -MY_WIDGET_NAMES = ["AnnotatorJ"] +MY_WIDGET_NAMES = ["AnnotatorJ","AnnotatorJExport"] ''' @pytest.mark.parametrize("widget_name", MY_WIDGET_NAMES) @@ -20,19 +21,71 @@ def test_something_with_viewer(widget_name, make_napari_viewer, napari_plugin_ma ''' #''' -def test_widet_loading(qtbot,make_napari_viewer): +@pytest.fixture +def test_annotatorj_widget(): + def test_main_widget_loading(qtbot,make_napari_viewer): + viewer = make_napari_viewer() + pluginInstance=napari_annotatorj.AnnotatorJ(viewer) + qtbot.addWidget(pluginInstance) + num_dw = len(viewer.window._dock_widgets) + viewer.window.add_dock_widget(pluginInstance,name='AnnotatorJ') + assert len(viewer.window._dock_widgets) == num_dw + 1 + + # give some time for the plugin init function to complete successfully on a thread loading the unet model + sleep(10) + #assert pluginInstance.trainedUNetModel is not None, f'trainedUnetModel is None' + return pluginInstance + + return test_main_widget_loading +#''' + +@pytest.mark.parametrize("widget_name", MY_WIDGET_NAMES) +def test_all_widget_loading(widget_name, make_napari_viewer, napari_plugin_manager): + napari_plugin_manager.register(napari_annotatorj, name=MY_PLUGIN_NAME) viewer = make_napari_viewer() + num_dw = len(viewer.window._dock_widgets) + viewer.window.add_plugin_dock_widget( + plugin_name=MY_PLUGIN_NAME, widget_name=widget_name + ) + assert len(viewer.window._dock_widgets) == num_dw + 1 + +def test_widget_loading_extras(make_napari_viewer,qtbot): + viewer=make_napari_viewer() pluginInstance=napari_annotatorj.AnnotatorJ(viewer) qtbot.addWidget(pluginInstance) num_dw = len(viewer.window._dock_widgets) viewer.window.add_dock_widget(pluginInstance,name='AnnotatorJ') assert len(viewer.window._dock_widgets) == num_dw + 1 - # give some time for the plugin init function to complete successfully on a thread loading the unet model - sleep(10) - #assert pluginInstance.trainedUNetModel is not None, f'trainedUnetModel is None' -#''' + # add classes widget + pluginInstance.classesFrame=napari_annotatorj.ClassesFrame(pluginInstance.viewer,pluginInstance) + assert len(viewer.window._dock_widgets) == num_dw + 2 + + # add options widget + pluginInstance.openOptionsFrame() + assert len(viewer.window._dock_widgets) == num_dw + 3 + + # add colours widget + pluginInstance.addColourWidget() + assert len(viewer.window._dock_widgets) == num_dw + 4 + + # add training widget + pluginInstance.openTrainWidget() + assert len(viewer.window._dock_widgets) == num_dw + 5 + + # add help widget + pluginInstance.openHelpWidgetDock() + assert len(viewer.window._dock_widgets) == num_dw + 6 + + # add 3d widget + pluginInstance.open3DWidget() + assert len(viewer.window._dock_widgets) == num_dw + 7 + + # add filelist widget + pluginInstance.openFileListWidget() + assert len(viewer.window._dock_widgets) == num_dw + 8 + @pytest.fixture def im_layer(): - return Image(np.random.random(5,100,100),name='Image') \ No newline at end of file + return Image(np.array(np.random.random((100,100,3))),name='Image') \ No newline at end of file diff --git a/src/napari_annotatorj/_tests/test_reader.py b/src/napari_annotatorj/_tests/test_reader.py index 55a5cad..f0d52d5 100644 --- a/src/napari_annotatorj/_tests/test_reader.py +++ b/src/napari_annotatorj/_tests/test_reader.py @@ -9,7 +9,7 @@ def write_im_to_file(tmp_path): def write_func(filename): # write some fake data using your supported file format #my_test_file = str(tmp_path / "myfile.npy") - my_test_file = str(tmp_path / "myfile.tiff") + my_test_file = str(tmp_path / filename) original_data = np.random.rand(20, 20) #np.save(my_test_file, original_data) skimage.io.imsave(my_test_file, original_data) diff --git a/src/napari_annotatorj/build_napari_annotatorj.bat b/src/napari_annotatorj/build_napari_annotatorj.bat new file mode 100644 index 0000000..46f9a77 --- /dev/null +++ b/src/napari_annotatorj/build_napari_annotatorj.bat @@ -0,0 +1,9 @@ +echo "removing old files..." +rmdir /s /q build +rmdir /s /q pyinstaller + + +echo "building app napari_annotatorj..." + +SET scriptpath=%~dp0 +pyinstaller --noconfirm --clean --distpath pyinstaller --log-level=INFO "%scriptpath%\napari_annotatorj.spec" diff --git a/src/napari_annotatorj/hooks/hook-OpenGL.py b/src/napari_annotatorj/hooks/hook-OpenGL.py new file mode 100644 index 0000000..6a3a9aa --- /dev/null +++ b/src/napari_annotatorj/hooks/hook-OpenGL.py @@ -0,0 +1,70 @@ +# ------------------------------------------------------------------ +# Copyright (c) 2020 PyInstaller Development Team. +# +# This file is distributed under the terms of the GNU General Public +# License (version 2.0 or later). +# +# The full license is available in LICENSE.GPL.txt, distributed with +# this software. +# +# SPDX-License-Identifier: GPL-2.0-or-later +# ------------------------------------------------------------------ + + +""" +Hook for PyOpenGL 3.x versions from 3.0.0b6 up. Previous versions have a +plugin system based on pkg_resources which is problematic to handle correctly +under pyinstaller; 2.x versions used to run fine without hooks, so this one +shouldn't hurt. +""" + + +from PyInstaller.compat import is_win, is_darwin +from PyInstaller.utils.hooks import collect_data_files, exec_statement +import os +import glob + + +def opengl_arrays_modules(): + """ + Return list of array modules for OpenGL module. + e.g. 'OpenGL.arrays.vbo' + """ + statement = 'import OpenGL; print(OpenGL.__path__[0])' + opengl_mod_path = exec_statement(statement) + arrays_mod_path = os.path.join(opengl_mod_path, 'arrays') + files = glob.glob(arrays_mod_path + '/*.py') + modules = [] + + for f in files: + mod = os.path.splitext(os.path.basename(f))[0] + # Skip __init__ module. + if mod == '__init__': + continue + modules.append('OpenGL.arrays.' + mod) + + return modules + + +# PlatformPlugin performs a conditional import based on os.name and +# sys.platform. PyInstaller misses this so let's add it ourselves... +if is_win: + hiddenimports = ['OpenGL.platform.win32'] +elif is_darwin: + hiddenimports = ['OpenGL.platform.darwin'] +# Use glx for other platforms (Linux, ...) +else: + hiddenimports = ['OpenGL.platform.glx'] + + +# Arrays modules are needed too. +hiddenimports += opengl_arrays_modules() + + +# PyOpenGL 3.x uses ctypes to load DLL libraries. PyOpenGL windows installer +# adds necessary dll files to +# DLL_DIRECTORY = os.path.join( os.path.dirname( OpenGL.__file__ ), 'DLLS') +# PyInstaller is not able to find these dlls. Just include them all as data +# files. +if is_win: + datas = collect_data_files('OpenGL') diff --git a/src/napari_annotatorj/hooks/hook-freetype.py b/src/napari_annotatorj/hooks/hook-freetype.py new file mode 100644 index 0000000..cb516a7 --- /dev/null +++ b/src/napari_annotatorj/hooks/hook-freetype.py @@ -0,0 +1,4 @@ +# Hook required for IPython autocomplete + + +hiddenimports = ["freetype"] diff --git a/src/napari_annotatorj/hooks/hook-ipykernel.py b/src/napari_annotatorj/hooks/hook-ipykernel.py new file mode 100644 index 0000000..0b0afad --- /dev/null +++ b/src/napari_annotatorj/hooks/hook-ipykernel.py @@ -0,0 +1 @@ +hiddenimports = ["ipykernel.datapub"] diff --git a/src/napari_annotatorj/hooks/hook-napari.py b/src/napari_annotatorj/hooks/hook-napari.py new file mode 100644 index 0000000..a6709e1 --- /dev/null +++ b/src/napari_annotatorj/hooks/hook-napari.py @@ -0,0 +1,4 @@ +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files('napari') +hiddenimports = ['napari.__main__'] \ No newline at end of file diff --git a/src/napari_annotatorj/hooks/hook-parso.py b/src/napari_annotatorj/hooks/hook-parso.py new file mode 100644 index 0000000..80c152f --- /dev/null +++ b/src/napari_annotatorj/hooks/hook-parso.py @@ -0,0 +1,5 @@ +# Hook required for IPython autocomplete + +from PyInstaller.utils.hooks import collect_data_files + +datas = collect_data_files('parso') diff --git a/src/napari_annotatorj/hooks/hook-vispy.py b/src/napari_annotatorj/hooks/hook-vispy.py new file mode 100644 index 0000000..23051e9 --- /dev/null +++ b/src/napari_annotatorj/hooks/hook-vispy.py @@ -0,0 +1,10 @@ +from PyInstaller.utils.hooks import collect_data_files +from vispy.app.backends import CORE_BACKENDS + +datas = collect_data_files('vispy') + +hiddenimports = ["vispy.ext._bundled.six"] + +# adding all backends is required for vispy.sys_info() to work +hiddenimports += ["vispy.app.backends." + b[1] for b in CORE_BACKENDS] +hiddenimports += ["vispy.app.backends._test"]